앱 조합
ShellCalendarAdapter로 opt-shell 제품 프레임 안에 캘린더를 배치하는 경계
reopt design업데이트
1. 언제 ShellCalendarAdapter를 쓰나
Calendar만으로 충분하다면 그대로 두세요. 하지만 캘린더를 제품 화면 안에 넣으면서 공통 chrome, side panel, 상태 UX가 필요해지는 순간 ShellCalendarAdapter가 그 경계를 대신 잡아줍니다. opt-shell은 opt-ui와 제품 화면 사이의 런타임 제품 프레임 레이어이고, 이 어댑터는 ShellDataGridAdapter·ShellEditorAdapter와 같은 규약을 캘린더에 적용한 것입니다.
- 카드 프레임·헤더·푸터 같은 공통 chrome을 캘린더에 두르고 싶을 때.
- 미니월·캘린더 목록(좌측)이나 이벤트 상세·AI(우측) 같은 side panel을 붙일 때.
- loading·empty·error 상태 UX를 boundary로 일관되게 처리하고 싶을 때.
- ShellDataGridAdapter·ShellEditorAdapter와 같은 제품 프레임 규약으로 캘린더를 맞추고 싶을 때.
2. 기본 사용
ShellCalendarAdapter는 Omit<CalendarProps, "className" | "ariaLabel">를 확장합니다. 즉 모든 Calendar props를 그대로 forward하면서 chrome 슬롯을 얹습니다. title·description·toolbar·status·sidebar(좌측)·inspector(우측)·footer·chrome(기본 "card")이 어댑터가 추가하는 슬롯입니다.
"use client";
import {
createCalendarStore,
createEmptyCalendarSpec,
} from "@reopt-ai/opt-calendar";
import "@reopt-ai/opt-calendar/styles.css";
import { ShellCalendarAdapter } from "@reopt-ai/opt-shell";
import { useState } from "react";
export function TeamCalendar() {
const [store] = useState(() =>
createCalendarStore(createEmptyCalendarSpec("Asia/Seoul")),
);
return (
<ShellCalendarAdapter
// ── chrome 슬롯 (어댑터가 추가) ──
title="팀 캘린더"
description="주간 일정과 예약을 한 화면에서 관리합니다"
toolbar={<CalendarActions />}
status={<SyncBadge />}
sidebar={<MiniMonth />} // 좌측 레일
inspector={<EventDetails />} // 우측 레일
footer={<Legend />}
chrome="card" // 기본값
// ── Calendar props는 그대로 forward ──
store={store}
variant="events"
timeZone="Asia/Seoul"
weekStartsOn={1}
onEventActivate={(eventId) => selectEvent(eventId)}
/>
);
}- store — 모든 편집이 통과하는 단일 변경 경로
- view · variant · timeZone · weekStartsOn — 뷰/시간 설정
- remoteSource — useCalendarRemoteSource 윈도우 소스
- suggestion — AI shadow draft 오버레이
- onEventActivate · onDateChange · onViewChange — 상호작용 콜백
className·ariaLabel만 Calendar에서 제외됩니다. ariaLabel은 title에서 파생되고, 스타일 오버라이드는 어댑터 전용 className 시임으로 제공됩니다.
3. 상태 UX
어댑터는 loading·isEmpty+empty·error를 chrome boundary로 감쌉니다. 원격 소스의 isLoading·lastError를 그대로 연결하면 됩니다.
<ShellCalendarAdapter
store={store}
remoteSource={remote}
// 로딩 → chrome이 로딩 상태를 렌더
loading={remote.isLoading}
// isEmpty가 true일 때만 empty가 표시됨
isEmpty={eventCount === 0}
empty={{
title: "표시할 일정이 없습니다",
description: "새 이벤트를 추가하거나 다른 주간으로 이동하세요",
}}
// Error | ShellErrorState | string | null 모두 허용
error={remote.lastError}
/>empty는 ShellEmptyState(title/description/icon/actions) 또는 문자열을, error는 Error·ShellErrorState·문자열을 받습니다. 상태가 없으면 일반 캘린더가 렌더됩니다.
4. opt-shell과의 관계
ShellCalendarAdapter는 ShellProvider 없이도 독립 렌더됩니다. ShellProvider 아래에 두면 opt-palette 토큰(var(--opt-*))과 정책(density/테마)이 함께 해석되어 제품 프레임에 자연스럽게 맞물립니다. 이는 ShellDataGridAdapter·ShellEditorAdapter가 각각 opt-datagrid·opt-editor를 감싸는 방식과 동일합니다.
레이아웃 세부는 className 시임으로 덮어씁니다.
- boundaryClassName — 상태 boundary 래퍼
- cardClassName — chrome="card"의 카드 프레임
- bodyClassName — sidebar/캘린더/inspector 그리드 영역
- sidebarClassName · calendarPaneClassName · inspectorClassName — 각 레일/페인
- calendarClassName — 내부 Calendar 루트