Calendar 계층
이벤트·예약·AI 편집을 하나의 store와 엔진으로 다룰 수 있도록 기능/시나리오별로 나눈 문서 모음입니다.
reopt design업데이트
CalendarSpec 모델, 반복(RRULE)/타임존 엔진, 드래그·리사이즈 상호작용, 예약 라이프사이클, AI 스트리밍 draft, 원격 윈도우 소스를 담당합니다.
Package boundary: @reopt-ai/opt-calendar는 Calendar 컴포넌트, createCalendarStore, 반복/타임존 엔진, 예약·AI·원격 훅을 root export로 제공합니다. 구조 CSS는 @reopt-ai/opt-calendar/styles.css 한 번 import가 필요하고, 런타임 peer는 React/React DOM입니다. Core(spec/patch/stream/ catalog)는 ai·zod를 optional peer로만 둔 zero-runtime-dep 레이어입니다.
이벤트·예약·타임존·드래그·AI 스트리밍을 한 화면에서 실험하려면 Fullscreen Playground를 사용하세요.
Playground 열기1. 기능별/시나리오별 문서
하나의 엔진으로 범용 이벤트 캘린더, 예약/가용성, AI 편집, 원격 데이터 UX를 구성할 수 있습니다. 아래 문서에서 구현 패턴을 확인하세요.
시작하기
설치, styles.css import, 첫 Calendar 렌더링까지의 빠른 시작
핵심 개념
CalendarSpec 5-맵 모델, 단일 변경 경로(applyPatch), 엔진/뷰 레이어 아키텍처
이벤트 편집
이벤트 CRUD, 팝오버 에디터, 드래그·리사이즈 상호작용과 단일 undo 경로
반복 일정
RRULE 전개, occurrence 모델, this/following/all 인스턴스 단위 편집
타임존
TimeZoneAdapter, wall-clock↔instant 변환, DST 경계에서의 정확성
예약 · 가용성
EventType/AvailabilitySchedule/BookingSlot과 confirm→materialize 라이프사이클
AI 스트리밍
useCalendarSuggestion shadow draft, NDJSON 패치 스트림, 라이브 오버레이
원격 소스
useCalendarRemoteSource 윈도우 fetch/save, id-merge, 낙관적 pending
앱 조합
ShellCalendarAdapter로 opt-shell 제품 프레임 안에 캘린더를 배치하는 경계
스킬
Claude Code 스킬로 opt-calendar 설치·통합을 자동화합니다
릴리즈 노트
opt-calendar 버전별 변경 사항과 하이라이트
Hook 레퍼런스
useCalendar*, useCalendarRender, useCalendarNavigation, remote, suggestion
타입 레퍼런스
CalendarSpec, CalendarEvent, Booking, EventType 등 핵심 타입 정의
2. 시나리오 선택 가이드
| 시나리오 | 적합한 상황 | 핵심 API |
|---|---|---|
| 이벤트 캘린더 | 월/주/일/아젠다 뷰에서 이벤트를 만들고 드래그·리사이즈로 편집하는 범용 캘린더 | variantviewtimeZoneweekStartsOn |
| 예약 · 가용성 | 가용성 스케줄에서 슬롯을 생성하고 예약을 확정하면 이벤트로 materialize되는 Cal.com형 흐름 | variantcatalogtimeZone |
| AI 편집 | 자연어 지시를 NDJSON 패치 스트림으로 받아 shadow draft로 미리 보고 승인/폐기 | suggestioncatalogstore |
| 원격 데이터 | 보이는 윈도우만 fetch하고 편집을 batch save하는 원격 이벤트 소스 연동 | remoteSourcestoreonDateChange |
3. 아키텍처 모델
opt-calendar는 opt-editor의 AI-first 아키텍처(스키마 기반 스트리밍 + RFC 6902 JSON Patch)를 캘린더 도메인으로 옮긴 구조입니다. 아래 다섯 축이 전체 동작을 지탱합니다.
Single mutation path
드래그, 리사이즈, 팝오버 편집, AI 스트림이 모두 `store.applyPatch(ops)` 한 경로로 흐릅니다. 하나의 undo 히스토리를 공유합니다.
Additive-only CalendarSpec
events/calendars/availability/eventTypes/bookings 5개 flat 맵 + timeZone. 필드는 제거·재사용하지 않아 이전 spec이 새 코드에서 그대로 적용됩니다.
Injectable engine seams
`RecurrenceEngine`(기본 zero-dep)와 `TimeZoneAdapter`(기본 native Intl)는 하드 의존이 아니라 주입 지점입니다. rrule/Temporal로 교체 가능합니다.
Record + materialized event
Booking은 워크플로우 레코드이고, 확정 시 CalendarEvent로 materialize되어 그리드에 렌더됩니다(`booking.eventId ↔ event.bookingId`).
Windowed remote + shadow AI
원격 소스는 보이는 윈도우만 fetch/save하고, AI draft는 shadow store에서 스트리밍되어 승인 전 오버레이로 미리 보입니다.
4. API 매트릭스
Calendar props를 역할별로 묶었습니다. 모든 편집이 통과하는 store와 엔티티 catalog를 먼저 연결한 뒤 뷰 상태·지역화·상호작용을 붙이는 순서가 안전합니다.
Store & 데이터
모든 변경이 통과하는 단일 store 계약과 엔티티 카탈로그를 연결합니다.
store드래그·리사이즈·AI 스트림·팝오버 편집이 모두 통과하는 단일 변경 경로(createCalendarStore). 하나의 undo 히스토리를 공유합니다.
catalog엔티티 종류 레지스트리 + AI 프롬프트/jsonSchema. 기본값은 defaultCalendarCatalog.
remoteSourceuseCalendarRemoteSource가 반환하는 윈도우 원격 소스. 보이는 범위만 fetch하고 편집을 batch save합니다.
suggestionuseCalendarSuggestion/useCalendarAI의 shadow draft. 승인 전에도 변경분을 pending 오버레이로 렌더합니다.
뷰 상태
활성 뷰와 앵커 날짜를 controlled/uncontrolled 어느 쪽으로도 다룹니다.
view활성 뷰(month/week/day/agenda). 생략 시 내부 상태로 관리됩니다.
onViewChange뷰 전환 콜백. controlled 뷰를 구현할 때 사용합니다.
date앵커 날짜(controlled). 생략 시 내부 상태를 사용합니다.
onDateChange앵커 날짜 변경 콜백. 원격 소스의 visible window 동기화에 연결합니다.
variant제품 변형: "events"(기본) 또는 "booking".
지역화 & 시간
표시 타임존, 주 시작일, 로케일, 메시지 오버라이드.
timeZone표시 타임존(IANA). 뷰 위치 계산·반복 전개·드래그 수식이 이 값을 기준으로 정렬됩니다.
weekStartsOn주 시작 요일(0=일요일). 기본 0.
locale라벨/날짜 포매팅에 쓰는 BCP-47 로케일.
messagesUI 문자열 오버라이드(CalendarMessages 부분 집합).
timeGrid주/일 타임그리드 표시 옵션(TimeGridOptions): 시간 범위(startHour/endHour), 행 높이(hourHeight), 스냅(snapMinutes), 내부 스크롤(maxBodyHeight)과 scroll-to-now.
상호작용 & 접근성
이벤트 활성화 콜백과 캘린더 영역의 접근성 라벨.
onEventActivate이벤트가 활성화(편집 팝오버 열림)될 때 호출됩니다.
ariaLabel캘린더 region의 접근성 라벨.
className루트 className.
5. 기본 사용 코드
"use client";
import {
Calendar,
createCalendarStore,
createEmptyCalendarSpec,
} from "@reopt-ai/opt-calendar";
import "@reopt-ai/opt-calendar/styles.css";
import { useState } from "react";
export default function BasicCalendar() {
const [store] = useState(() => {
const spec = createEmptyCalendarSpec("Asia/Seoul");
spec.events.standup = {
id: "standup",
title: "팀 스탠드업",
start: "2026-07-06T09:00:00+09:00",
end: "2026-07-06T09:15:00+09:00",
rrule: "FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR",
};
return createCalendarStore(spec);
});
// 모든 편집(드래그/리사이즈/팝오버)이 store.applyPatch 한 경로로 흐릅니다.
return <Calendar store={store} timeZone="Asia/Seoul" weekStartsOn={1} />;
}6. 원격 + AI 통합 구성
프로덕션에서는 보이는 윈도우만 원격에서 로드하고, 자연어 지시를 shadow draft로 미리 본 뒤 승인하는 조합을 권장합니다.
"use client";
import {
Calendar,
createCalendarStore,
createEmptyCalendarSpec,
defaultCalendarCatalog,
useCalendarRemoteSource,
useCalendarSuggestion,
} from "@reopt-ai/opt-calendar";
import "@reopt-ai/opt-calendar/styles.css";
import { useState } from "react";
export function ProductionCalendar() {
const [store] = useState(() =>
createCalendarStore(createEmptyCalendarSpec("Asia/Seoul")),
);
// 1) 보이는 윈도우만 원격에서 로드하고 편집을 batch save.
// Calendar가 뷰/날짜 변경 시 onWindowChanged를 자동 호출한다.
const remote = useCalendarRemoteSource(store, {
loadEvents: async ({ window, signal }) => ({
events: await fetchEvents(window, signal),
}),
savePatches: ({ ops, changedEventIds }) => persistOps(ops, changedEventIds),
});
// 2) 자연어 지시를 NDJSON 패치 스트림으로 받아 shadow draft로 미리보기
const ai = useCalendarSuggestion(store);
return (
<Calendar
store={store}
catalog={defaultCalendarCatalog}
timeZone="Asia/Seoul"
remoteSource={remote}
suggestion={ai.suggestion}
/>
);
}