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 열기실제 제품 화면 패턴은 Explore opt-calendar에서 확인하세요. 이벤트 캘린더, public booking selector, AI draft review, remote source 예제가 분리되어 있습니다.
Explore 열기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 제품 프레임 안에 캘린더를 배치하는 경계
스킬 지원
전용 설치 스킬 유무와 opt-shell 연동, Calendar 수동 설정 범위를 구분합니다
릴리즈 노트
opt-calendar 버전별 변경 사항과 하이라이트
Hook 레퍼런스
useCalendar*, useCalendarRender, useCalendarNavigation, remote, suggestion
타입 레퍼런스
CalendarSpec, CalendarEvent, Booking, EventType 등 핵심 타입 정의
2. 시나리오 선택 가이드
| 시나리오 | 적합한 상황 | 핵심 API |
|---|---|---|
| 이벤트 캘린더 | 월/주/일/아젠다 뷰에서 이벤트를 만들고 드래그·리사이즈로 편집하는 범용 캘린더 | variantviewtimeZoneweekStartsOn |
| 예약 · 가용성 | 가용성 스케줄에서 슬롯을 생성하고, public 예약 셀렉터와 full 운영자 패널을 분리하는 Cal.com형 흐름 | variantbookingModecatalogtimeZone |
| AI 편집 | 자연어 지시를 NDJSON 패치 스트림으로 받아 shadow draft로 미리 보고 승인/폐기 | suggestioncatalogstore |
| 원격 데이터 | 보이는 윈도우만 fetch하고 편집을 batch save하는 원격 이벤트 소스 연동 | remoteSourcestoreonDateChange |
3. 제품화 체크리스트
문서의 API 설명만으로는 실제 제품 표면이 충분히 보이지 않습니다. 아래 항목을 기준으로 이벤트 캘린더, 부킹 셀렉터, AI 검토, 원격 데이터 연동을 각각 독립된 제품 화면으로 확인하세요.
Events surface
월/주/일/아젠다/타임라인은 같은 이벤트 팝오버, hover/focus 언어, store.applyPatch 변경 경로를 공유합니다.
Booking surface
고객 예약 화면은 bookingMode="public", 운영자 가용성·예약 패널은 bookingMode="full"로 분리합니다.
Keyboard and focus
월 그리드는 화살표 키 2D 이동(±1일/±1주), 주/일 타임그리드는 좌우 화살표로 날짜 컬럼 이동(roving tabindex). 명확한 focus ring과 생성/예약 폼 첫 필드 autofocus를 검증합니다.
Remote + AI review
visible window remote source와 shadow AI draft를 결합해 서버 데이터와 생성 편집을 같은 승인 경로로 처리합니다.
Playable examples
Explore 예제와 fullscreen Playground에서 이벤트, 부킹, AI draft, remote source를 실제 제품 화면처럼 확인합니다.
Verification gates
패키지 test/typecheck/lint/build와 e2e/opt-calendar.spec.ts가 문서·플레이그라운드·Explore 경로까지 막습니다.
4. 아키텍처 모델
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에서 스트리밍되어 승인 전 오버레이로 미리 보입니다.
5. 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".
bookingModebooking 변형의 표면 구성: "public"은 고객용 날짜/시간 셀렉터만, "full"은 availability 편집과 bookings 운영 패널까지 함께 렌더합니다.
지역화 & 시간
표시 타임존, 주 시작일, 로케일, 메시지 오버라이드.
timeZone표시 타임존(IANA). 뷰 위치 계산·반복 전개·드래그 수식이 이 값을 기준으로 정렬됩니다.
weekStartsOn주 시작 요일(0=일요일). 기본 0.
locale라벨/날짜 포매팅에 쓰는 BCP-47 로케일.
messagesUI 문자열 오버라이드(CalendarMessages 부분 집합).
timeGrid주/일 타임그리드 표시 옵션(TimeGridOptions): 시간 범위(startHour/endHour), 행 높이(hourHeight), 스냅(snapMinutes), 내부 스크롤(maxBodyHeight)과 scroll-to-now.
상호작용 & 접근성
이벤트 활성화 콜백과 캘린더 영역의 접근성 라벨. 월/주/일 그리드는 roving tabindex 기반 화살표 키 내비게이션을 기본 제공합니다.
onEventActivate이벤트가 활성화(편집 팝오버 열림)될 때 호출됩니다.
ariaLabel캘린더 region의 접근성 라벨.
className루트 className.
6. 기본 사용 코드
"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} />;
}7. 원격 + 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}
/>
);
}