Kleri UI

Component Preview

Kleri UI / Components / Calendar

Calendar

Scheduling with day, week, month and agenda views, drag-and-drop, recurring events, time zones and Google, Outlook and iCalendar integrations.

KleriCalendar

Drag on the grid to schedule, drag events to move them, pull their bottom edge to resize. Add guests in the editor to see their availability and suggested times. Press ? for shortcuts.

Usage
<script lang="ts">
  import {
    CalendarStore,
    KleriCalendar,
    createMemoryProvider,
    createGoogleCalendarProvider
  } from '@kleri/ui/calendar';

  const store = new CalendarStore({
    providers: [
      createMemoryProvider({ calendars: [{ id: 'work', name: 'Work' }] })
    ],
    persistKey: 'my-app-calendar'
  });

  let view = $state('week');
  let date = $state(new Date());
</script>

<div class="h-[720px]">
  <KleriCalendar
    {store}
    bind:view
    bind:date
    self={{ email: "you@kleri.org", name: "You" }}
    {contacts}
    {integrations}
  />
</div>

Props

Hide Weekends

false

Week Numbers

false

Read Only

false

12-hour Clock

true

Dim Past Events

true

Integrations

Every source is a CalendarProvider. Google Calendar, Microsoft Graph and iCalendar feeds ship built in, with token refresh, retries and backoff. Pass integrations to power the “Add calendar” dialog.

Providers
import { createGoogleCalendarProvider, createMicrosoftCalendarProvider, createIcsFeedProvider } from '@kleri/ui/calendar';

// Your app owns OAuth; the adapter only needs a token getter.
store.addProvider(
  createGoogleCalendarProvider({
    account: user.email,
    getAccessToken: ({ forceRefresh }) => auth.getGoogleToken({ forceRefresh })
  })
);

store.addProvider(
  createMicrosoftCalendarProvider({
    account: user.email,
    getAccessToken: () => msal.acquireTokenSilent({ scopes: ['Calendars.ReadWrite'] }).then((r) => r.accessToken)
  })
);

// Read-only iCalendar feeds (Apple, holidays, shared "secret address" URLs…).
// Most feeds lack CORS headers, so route them through your backend.
store.addProvider(
  createIcsFeedProvider({
    url: 'webcal://example.com/team.ics',
    proxy: (url) => `/api/ics?url=${encodeURIComponent(url)}`
  })
);

// Anything else: implement the CalendarProvider interface.
const myBackend: CalendarProvider = {
  id: 'acme', kind: 'acme', label: 'Acme',
  capabilities: { write: true, freeBusy: false, recurrence: 'provider' },
  listCalendars: () => api.calendars(),
  listEvents: ({ calendarId, start, end, signal }) => api.events(calendarId, start, end, { signal }),
  createEvent: (calendarId, draft) => api.create(calendarId, draft)
};

Custom views

Views are plain definitions. Timeline or chart views plug in alongside the built-ins and reuse the same data, popovers and scheduling flows through the calendar context.

Custom view
import { defaultCalendarViews, type CalendarViewDefinition } from '@kleri/ui/calendar';
import TimelineView from './TimelineView.svelte';

const timelineView: CalendarViewDefinition = {
  id: 'timeline',
  label: 'Timeline',
  shortcut: 'l',
  component: TimelineView,
  range: (date, ctx) => ({ start: startOfWeek(date, ctx.weekStartsOn), end: addDays(startOfWeek(date, ctx.weekStartsOn), 14) }),
  step: (date, dir) => addDays(date, 14 * dir),
  title: (range, ctx) => formatDateRange(ctx.locale, range.start, addDays(range.end, -1))
};

// <KleriCalendar views={[...defaultCalendarViews, timelineView]} />

// Inside TimelineView.svelte — the calendar context gives you everything:
// const ctx = getCalendarContext();
// ctx.actions.open(occurrence, element)      → details popover
// ctx.actions.move(occurrence, start, end)   → drag-to-reschedule, with recurring prompts
// ctx.actions.create({ start, end, allDay }) → quick create / scheduling dialog