프로젝트 기록

[React-draggable-selector] 셀렉터 라이브러리 제작하기 (1) - 개요 및 기능탐색

lerrybe 2023. 8. 12. 14:54

업데이트 사항 (w. 2023/08/24)

https://lerryroad.tistory.com/136

컴포넌트 props 인터페이스를 대폭 수정, 구현 구조를 변경하여 버전 2.X.X로 업데이트했습니다. outdated된 내용 또한 아래 본문에 표시해뒀는데 오래된 버전에 대한 내용은 🚫, 업데이트된 버전에 대한 내용은 그 옆에 ✅로 작성했으니 참고하면 좋을 것 같습니다.

 

 

npm & github

🐱 Github

🐱 live example

 


들어가며

현재 진행중인 프로젝트에서 드래그가 가능한 시간 선택기를 만들 필요가 있었는데, 기존의 라이브러리들은 스타일링 커스텀 + 요일별 선택이 구현되어 있지 않아 직접 구현했었습니다.

 

Draggable-time-selector 구현기 (with REACT, TS)

⏰ Issue 현재 참여 중인 프로젝트에서 드래그로 슬롯들을 선택해야하는 부분이 있었다. 재밌겠다!! 하며 바로 이슈 올리고 물어버리기 일반적으로 선택하고 싶은 영역을 클릭으로 선택하는 간단

lerryroad.tistory.com

간결한 동작 방식에 비해 코드 양이 있고, 내부 로직을 모른 채 인터페이스만 볼 수 있다면 더 편하지 않을까? 해서 아직 개선점이 많지만 라이브러리로 만들어보기로 결심했습니다.

 

 

라이브러리 개요

 

소개글

🌀 Select with a simple drag, Feel free to style it!

🚫 React-Draggable-Selector is a customizable component that allows users to easily select time ranges by dragging. 

Its main advantages are its intuitive drag-to-select functionality and the freedom to apply custom styling. You can select a time zone by day of the week or by date. Let's get started!

✅ React-Draggable-Selector is a react component that allows users to easily select time ranges by dragging.

This package is for react applications, and uses @emotion for styling and dayjs for date and time management. Its main advantages are its intuitive drag-to-select functionality. You can select a time zone by day of the week or by date. Let's get started!

 

 

기능 overview

리액트 환경에서 동작할 수 있게 구성한다.

사용자가 내려주는 Date 타입 배열에 따라 선택된 날짜를 구성한다.

🚫 커스텀 스타일이 가능하게 한다. (✅ 최소 기능과 최소 스타일링에서 시작해서, 점차 살을 붙여나가기로 합니다.)

 

 

🛎 개발 기록 및 과정

1. 환경설정

https://blog.harveydelaney.com/creating-your-own-react-component-library/

 

Creating a React Component Library using Rollup, Typescript, Sass and Storybook

Learn how how you can quickly and easily set up your own React Component library using Rollup, TypeScript, Sass and Storybook.

blog.harveydelaney.com

환경세팅은 위 글의 내용을 많이 참고해서 진행했습니다.

더 빠른 빌드 시간을 위해 Vite를 사용한 환경에서 개발하기로 했는데, vite는 더 빠른 빌드 시간을 제공하고 애플리케이션의 성능을 향상시키기 위해 Rollup을 사용합니다.

 

CRA와 Vite

CRA는 애플리케이션을 빌드할 때 시간이 더 걸릴 수 있는데 이는 CRA가 애플리케이션을 빌드할 때 Webpack을 사용하기 때문입니다. 반면 Vite Rollup을 사용하는데 개발 중에 더 빠른 빌드 시간을 제공, 애플리케이션의 성능을 향상시킬 수 있다는 장점이 있습니다. 웹팩은 애플리케이션 번들링 시, 롤업은 라이브러리 번들링 시 주로 사용한다고 하네요.

 

 

2. 사용자 경험

만약 이 라이브러리를 사용하는 사용자라면 어떤게 필요할까? 

요일별/날짜별 시간 선택이 가능했으면 좋겠고, document나 예제 페이지를 통해 쉽게 살펴볼 수 있으면 좋을 것 같습니다.

 

 

3. 기존 라이브러리 탐색

 

GitHub - bibekg/react-schedule-selector: A mobile-friendly when2meet-style grid-based schedule selector

A mobile-friendly when2meet-style grid-based schedule selector - GitHub - bibekg/react-schedule-selector: A mobile-friendly when2meet-style grid-based schedule selector

github.com

유사하게 동작하는 라이브러리에서는 어떻게 하는지 살펴봤는데 기본적으로 Props에 내려주는 방식으로 내부를 컨트롤 할 수 있게 되어있었습니다. headless UI처럼 상태처리에만 집중할 수도 있겠지만 일단 컴포넌트 하나로 쉽게 사용할 수 있게 하고 싶어 마찬가지로 Props로 속성들을 내려줄 수 있게 디자인했습니다.

 

 

4. 기술 스택 및 의존성

react, 🚫styled-component ✅ @emotion/react, @emotion/styled, dayjs

기존의 moment를 제거하고, dayjs로 교체했습니다.

https://github.com/you-dont-need/You-Dont-Need-Momentjs

위 링크에 들어가보면 moment vs 타 시간날짜 계산 라이브러리에 대한 비교가 잘 되어있고, 특히 번들 사이즈에서 moment의 단점을 살펴볼 수 있었습니다. 실제 npm 사이트에서도 확인해보니 사이즈 차이를 확인해볼 수 있었습니다. 

 

moment js
dayjs

 

그렇다면 왜 dayjs?

  • 작은 번들 사이즈, 추가 플러그인을 통하여 필요한 기능만 확장해 사용할 수 있다.
  • moment.js와 인터페이스가 유사하여 마이그레이션이 쉽다. 

이러한 이유에서 moment.js에서 dayjs로 관련 내용들을 교체하는 작업을 가졌습니다. 인터페이스가 유사해서 교체하는 과정도 편했고, 필요한 플러그인들은 import 해서 사용하면 되는거라 큰 어려움이 없었습니다. 사실 날짜나 시간을 계산하는 함수를 직접 작성할까도 고민했었는데 다양한 포맷을 호환될 수 있게 하고 싶기도 해 이 부분에 집중한 라이브러리를 활용하기로 결정했습니다.

 

 

5. 주요기능 및 props 살펴보기

(우측 컨트롤은 playground를 위함)

 

(1) 주요 데이터 컨트롤하기

- 원하는 날짜를 선택할 수 있다.

- 날짜별, 요일별로 시간대를 선택할 수 있다. (DATE ver. / DAY ver.)

    - 요일별로 설정되어 있을 때는 해당 요일에 해당하는 날짜들의 시간이 모두 선택된다.

- 한 슬롯의 시간 크기를 결정할 수 있다. (시간 간격, Time unit)

- 시작 시간과 끝 시간을 선택할 수 있다. 

- 표기될 언어를 한/영 선택할 수 있다. 

- 표시될 date format과 time format을 지정해줄 수 있다.

 

- (Updated in 2.x.x) props 변경

  🚫 startTime → ✅ minTime

  🚫 endTime → ✅ maxTime

  🚫 language ❌

  🚫 selectedTimeSlots → ✅ timeSlots

  🚫 setSelectedTimeSlots → ✅ setTimeSlots

  

docs/1. control core data

 

(2) 셀렉터 스타일 컨트롤하기

- 셀렉터의 외곽 및 스크롤 스타일을 설정할 수 있다.

- width(min-width, max-width), height(min-height, max-height)

- margin, padding, scrollColor, scrollBgColor, scrollWidth(스크롤 폭)

 

- (Updated in 2.x.x) props 변경

  🚫 width, height, margin, padding  

  🚫 scrollColor, scrollBgColor, scrollWidth  

  🚫 minWidth, minHeight  

  ✅ (new!) slotsMarginTop, slotsMarginLeft, slotsContainerBorder, slotsContainerBorderRadius

 

docs/2. control selector style

 

(3) 각 슬롯(셀) 스타일 컨트롤하기

- 슬롯 (셀) 스타일을 설정할 수 있다.

- 가로 간격, 세로 간격, 셀 높이, borderRadius, borderStyle,

- 셀 기본 색상, 호버된 셀 색상, 선택된 셀 색상, 선택 불가한 영역의 셀 색상 등

 

- (Updated in 2.x.x) props 변경

  🚫 slotRowGap, slotColumnGap, slotBorderRadius, slotBorderStyle , slotMinWidth

 

docs/3. control slot (cell) style

 

(4) row 라벨 스타일 컨트롤하기 (시간을 나타내는 라벨)

- 라벨 wrapper의 padding, margin 컨트롤

- 라벨 각각의 padding, margin 컨트롤

 

- (Updated in 2.x.x) props 변경

  🚫 Delete all props is this section  ❌

 

docs/4. control row label style

 

 

(5) column 라벨 스타일 컨트롤하기 (날짜를 나타내는 라벨)

- 라벨 wrapper의 padding, margin 컨트롤

- 라벨 각각의 padding, margin 컨트롤

 

- (Updated in 2.x.x) props 변경

  🚫 Delete all props is this section  ❌

 

docs/4. control column label style

 


🛎 간단한 사용

🚀 Quick Start

1. Installation

Use npm package manager to install

$ npm install react-draggable-selector

or install with yarn

$ yarn add react-draggable-selector

 

2. Usage

Take out the component in your React project.

 

1. When using Javascript:

// ✅ 2.x.x updated version

import { useState } from 'react';
import { DraggableSelector } from "react-draggable-selector";

function App () {
  const [dates, setDates] = useState([]);  // Should inject sorted, not duplicated Date[]
  const [times, setTimes] = useState([]);

  return (
    <DraggableSelector
      minTime={9}              // required
      maxTime={14}             // required
      dates={dates}            // required, required default: []
      timeSlots={times}        // required, required default: []
      setTimeslots={setTimes}  // required
    />
  );
};

export default App;

 

2. When using Typescript:

// ✅ 2.x.x updated version 

import { useState } from 'react';
import { DraggableSelector, TimeSlot } from "react-draggable-selector";

/*
interface TimeSlot {
  day: number;
  date: string;
  minTime: string;  // '09:00', '14:00'
  maxTime: string;  // '09:30', '14:30'
}
*/

function App () {
  const [dates, setDates] = useState<Date[]>([]);  // Should inject sorted, not duplicated Date[]
  const [times, setTimes] = useState<TimeSlot[]>([]);

  return (
    <DraggableSelector
      minTime={9}              // required, type: 'number'
      maxTime={14}             // required, type: 'number'
      dates={dates}            // required, type: 'Date[]', required default: []
      timeSlots={times}        // required, type: 'TimeSlot[]', required default: []
      setTimeslots={setTimes}  // required, type: 'React.Dispatch<React.SetStateAction<TimeSlot[]>>'
    />
  );
};

export default App;

 


🛎 나가며

이번 글은 셀렉터 라이브러리 개발의 간단한 개요와 배경, 인터페이스 소개에 대한 내용이었습니다.

 

만들었던 기능을 간단히 떼어내서 배포하는 정도 +@ 의 작업이지 않을까 생각했엇는데 막상 라이브러리로 만들려고 하다보니 생각보다 신경써야할 부분들이 있었어요.

 

일단 컴포넌트가 독립적으로 하는 일이 명확하다고 생각했는데도 프로젝트 내부에서 이를 떼어내는 작업이 마냥 간단하지는 않았던 것 같습니다. 기존에 작성한 코드들이 생각보다 불필요한 부분에서 의존성이 있어서 이를 정리해야했고 코드를 보면서 제가 썼던 코드인데도 흐름파악이 어려웠던 부분이 있었습니다.

일단 하나의 함수가 의도한 한 가지 일만 할 수 있도록 정리하고, 그에 맞는 네이밍을 지어서 기능이 추가적으로 붙었을 때도 처음과 같은 무게로 느낄 수 있도록 설계해야겠다는 생각이 간절히 들었어서 이렇게 기록에 남깁니다.

 

다음 글에서는 라이브러리 내부를 살펴보도록 합시다! 🚛 🚛 🚛