- 컴포넌트1
- calendar
- date-picker
- headless
- 그외
라이브러리 특징:
- react-aria - Adobe Spectrum 팀의 headless. 스타일 없이 로직과 접근성만.
useCalendar,useDatePicker훅 기반. ARIA 구현 꼼꼼. 일정 표시는 직접 구현. - Schedule-X - 최신. React/Vue/Angular 어댑터. 드래그/리사이즈 지원. 월/주/일 뷰. 가벼움. 레퍼런스 적음.
- FullCalendar - 가장 오래됨. 플러그인 아키텍처. 타임라인, 리소스 뷰 등 고급 기능. 일부 유료, 번들 큼.
- react-big-calendar - 순수 React. Google Calendar 스타일. moment/date-fns/dayjs 선택 가능. 타입 지원 아쉬움.
| 상황 | 추천 |
|---|---|
| 완전 커스텀 UI 필요 | react-aria + 직접 구현 |
| 빠르게 기본 기능 필요 | react-big-calendar |
| 모던한 DX, 가벼움 중시 | Schedule-X |
| 엔터프라이즈급 기능 | FullCalendar |
Footnotes
import {
addDays,
addMonths,
eachDayOfInterval,
eachWeekOfInterval,
startOfMonth,
} from 'date-fns'
const startOfMonthDate = startOfMonth(new Date())
const matrix = eachWeekOfInterval({
start: startOfMonthDate,
end: addMonths(startOfMonthDate, 1),
}).map((weekDay) => {
const startDate = new Date(weekDay)
return eachDayOfInterval({
start: startDate,
end: addDays(startDate, 6),
})
})
캘린더 이벤트 겹침 처리 알고리즘
겹치는 이벤트들을 나란히 배치하기 위한 레인 할당:
interface Event {
id: string
start: number
end: number
}
interface PositionedEvent extends Event {
lane: number
totalLanes: number
}
function calculatePositions(events: Event[]): PositionedEvent[] {
// 1. 시작 시간 순 정렬
const sorted = [...events].sort((a, b) => a.start - b.start || a.end - b.end)
// 2. 겹침 그룹 찾기 + 레인 할당
const result: PositionedEvent[] = []
let group: Event[] = []
let groupEnd = 0
sorted.forEach((event) => {
if (group.length === 0 || event.start < groupEnd) {
group.push(event)
groupEnd = Math.max(groupEnd, event.end)
} else {
processGroup(group, result)
group = [event]
groupEnd = event.end
}
})
if (group.length) processGroup(group, result)
return result
}
function processGroup(group: Event[], result: PositionedEvent[]) {
const activeLanes: number[] = []
group.forEach((event) => {
// 사용 가능한 가장 낮은 레인 찾기
let lane = activeLanes.findIndex((end) => end <= event.start)
if (lane === -1) lane = activeLanes.length
activeLanes[lane] = event.end
result.push({ ...event, lane, totalLanes: 0 })
})
// totalLanes 업데이트
const maxLane =
Math.max(...result.slice(-group.length).map((e) => e.lane)) + 1
result.slice(-group.length).forEach((e) => (e.totalLanes = maxLane))
}
// CSS 적용: left = lane/totalLanes * 100%, width = 100%/totalLanes
dayjs로 특정 날짜가 포함된 주의 모든 날짜 가져오기
import dayjs from 'dayjs'
import isoWeek from 'dayjs/plugin/isoWeek'
dayjs.extend(isoWeek)
// startOf('week') → 일요일 시작
// startOf('isoWeek') → 월요일 시작 (캘린더 UI에 주로 사용)
function getWeekDays(date, iso = true) {
const start = dayjs(date).startOf(iso ? 'isoWeek' : 'week')
return Array.from({ length: 7 }, (_, i) =>
start.add(i, 'day').format('YYYY-MM-DD')
)
}
getWeekDays('2025-10-28') // ['2025-10-27', ..., '2025-11-02']