Sidebar Plugin

The Sidebar plugin adds a calendar management sidebar to your DayFlow calendar. It includes a mini calendar for date navigation, a calendar list with visibility toggles, and full calendar CRUD operations (create, rename, recolor, merge, delete, import/export).

Installation

Install the plugin package:

npm install @dayflow/plugin-sidebar
pnpm add @dayflow/plugin-sidebar
yarn add @dayflow/plugin-sidebar
bun add @dayflow/plugin-sidebar

Usage

import { useCalendarApp, DayFlowCalendar } from '@dayflow/react'; // or '@dayflow/vue', '@dayflow/svelte', '@dayflow/angular'
import { createSidebarPlugin } from '@dayflow/plugin-sidebar';

function MyCalendar() {
  const sidebarPlugin = createSidebarPlugin({
    width: 280,
    createCalendarMode: 'modal',
  });

  const calendar = useCalendarApp({
    views: [
      /* your views */
    ],
    plugins: [sidebarPlugin],
  });

  return <DayFlowCalendar calendar={calendar} />;
}

Configuration

PropertyTypeDefaultDescription
widthnumber | string'240px'Width of the sidebar (e.g. 280 or '20rem')
miniWidthstring'50px'Width of the sidebar when collapsed
initialCollapsedbooleanfalseWhether the sidebar starts collapsed
createCalendarMode'inline' | 'modal''inline'How the "create calendar" form is displayed
colorPickerMode'blossom' | 'default''default'Which color picker component to use
onSubscribeCalendar(calendar, events) => Promise<void>undefinedCallback triggered after a new subscription
onLoadSubscription(calendar) => Promise<void>undefinedCustom loader for existing subscriptions
onReorder(calendars: CalendarType[]) => void | Promise<void>undefinedCallback triggered after calendar reordering
render(props: CalendarSidebarRenderProps) => TNodeundefinedFull override for the sidebar UI
renderSidebarHeader(args: SidebarHeaderSlotArgs) => TNodeundefinedCustom sidebar header renderer
renderCalendarContextMenu(calendar, onClose) => TNodeundefinedCustom context menu renderer for calendar items
renderCreateCalendarDialog(props) => TNodeundefinedCustom create-calendar dialog renderer

Features

The sidebar provides a ready-made layout with visibility toggles, calendar color swatches, and collapse controls. It also supports calendar creation and context menus, allowing users to add, edit colors, and delete calendars.

Built-in features include:

  • Mini Calendar - A compact month view for quick date navigation
  • Calendar List - Toggle visibility of individual calendars with color-coded checkboxes
  • Create Calendar - Add new calendars with custom name and color
  • Rename / Recolor - Right-click a calendar to rename or change its color
  • Calendar Reordering - Drag and drop calendars in the list to reorder them. Use the onReorder callback to persist the new order to your backend.
  • Merge Calendars - Move all events from one calendar into another
  • Delete Calendar - Remove a calendar with a confirmation step (or merge first)
  • Import / Export - Import .ics files or export calendars to .ics
  • Subscribe to Calendar - Subscribe to remote .ics feeds by URL; subscribed calendars display a badge in the sidebar list

Live Showcase

Explore the built-in sidebar UI and a custom framework-native implementation below.

What to explore

  • Visibility Toggles: Hide and show calendars to see the views update instantly.
  • Drag & Sync: Drag events between views; the sidebar keeps upcoming highlights in sync.
  • Collapse: Toggle the sidebar to reclaim space when focusing on dense week views.

Subscribe to Calendar

The sidebar includes a built-in subscribe feature that lets users add remote ICS calendar feeds. To trigger it, right-click any calendar or the empty area in the sidebar to open the context menu and select Subscribe to Calendar, then enter any publicly accessible .ics URL.

DayFlow fetches the file, parses the events, and creates a new calendar automatically. Subscribed calendars display a small badge icon in the sidebar list to distinguish them from locally created calendars.

Automatic Loading & Deduplication

When you provide a calendar with a subscription.url in the initial configuration, the Sidebar plugin automatically fetches the latest events on mount.

To prevent visual duplicates when combining locally cached events with fresh subscription data, DayFlow implements ID-based deduplication:

  • If an event from a subscription has the same id as an existing event in the core store, the subscription version takes precedence.
  • This ensures your UI always shows the most up-to-date information without "doubling up" on events after a page reload.

Persisting Subscribed Calendars

DayFlow identifies subscribed calendars by the presence of a subscription object on the CalendarType.

Default Behavior for Subscriptions:

  • Read-only: UI mutations (editing title, changing calendar, deleting) are disabled via canMutateFromUI().
  • Non-draggable: Drag-and-drop actions (moving, resizing) are disabled.
  • Hidden Notes: If an event has no description, the "Note" field is hidden in the detail panel to keep the UI clean.

To allow users to modify subscribed events or move them around, you can explicitly override these protections by setting readOnly: false on the calendar object:

const calendar = useCalendarApp({
  calendars: [
    {
      id: 'team-ics',
      name: 'Team Calendar',
      subscription: {
        url: 'https://example.com/calendar.ics',
        status: 'ready',
      },
      // Override default protections:
      readOnly: false, // Enables both UI mutations and drag-and-drop
      colors: {
        /* ... */
      },
    },
  ],
});

When saving calendar state to a backend, preserve the subscription field and restore it on the next load.

Note: DayFlow does not auto-refresh subscribed feeds. To keep events up to date, implement periodic fetching in your app and update events via app.addEvent() / app.removeEvent().

Fully Custom Sidebar

You can replace the sidebar with your own component by passing a render function to createSidebarPlugin. It receives real-time calendar state and helper actions.

import {
  createSidebarPlugin,
  type CalendarSidebarRenderProps,
} from '@dayflow/plugin-sidebar';

const CustomSidebar = ({
  app,
  calendars,
  toggleCalendarVisibility,
  toggleAll,
  isCollapsed,
  setCollapsed,
}: CalendarSidebarRenderProps) => {
  if (isCollapsed) {
    return (
      <div className='p-2'>
        <button onClick={() => setCollapsed(false)}>→</button>
      </div>
    );
  }

  return (
    <aside className='flex h-full flex-col gap-4 p-4 bg-slate-50 border-r'>
      <header className='flex items-center justify-between'>
        <h3 className='font-semibold'>My Workspace</h3>
        <button onClick={() => setCollapsed(true)}>←</button>
      </header>

      <nav className='space-y-1'>
        {calendars.map(calendar => (
          <label
            key={calendar.id}
            className='flex items-center gap-2 cursor-pointer'
          >
            <input
              type='checkbox'
              checked={calendar.isVisible}
              onChange={e =>
                toggleCalendarVisibility(calendar.id, e.target.checked)
              }
            />
            <span
              className='w-3 h-3 rounded-full'
              style={{ backgroundColor: calendar.colors.lineColor }}
            />
            {calendar.name}
          </label>
        ))}
      </nav>

      <div className='mt-auto pt-4 border-t text-xs text-slate-500'>
        Total Events: {app.getEvents().length}
      </div>
    </aside>
  );
};

const sidebarPlugin = createSidebarPlugin({
  render: props => <CustomSidebar {...props} />,
});

Custom Context Menu

You can replace the default right-click menu for calendar items:

createSidebarPlugin({
  renderCalendarContextMenu: (calendar, onClose) => (
    <div className='bg-white shadow-lg border rounded p-2'>
      <button
        onClick={() => {
          console.log('Custom action');
          onClose();
        }}
      >
        Custom Action for {calendar.name}
      </button>
    </div>
  ),
});

Custom Create Calendar Dialog

You can replace the default create-calendar dialog:

createSidebarPlugin({
  renderCreateCalendarDialog: ({ onCreate, onClose }) => (
    <MyCustomDialog onSave={onCreate} onCancel={onClose} />
  ),
});

Custom Sidebar Header

You can replace the default sidebar header (the area with the "Calendars" title and collapse toggle):

createSidebarPlugin({
  renderSidebarHeader: ({ isCollapsed, onCollapseToggle }) => (
    <div className='flex items-center justify-between p-4 border-b'>
      {!isCollapsed && <span className='font-bold text-lg'>My Calendars</span>}
      <button
        onClick={onCollapseToggle}
        className='p-1 hover:bg-slate-100 rounded'
      >
        {isCollapsed ? '→' : '←'}
      </button>
    </div>
  ),
});

On this page