侧边栏插件

侧边栏插件为 DayFlow 日历添加日历管理侧边栏。包含用于日期导航的迷你日历、带可见性切换的日历列表,以及完整的日历 CRUD 操作(创建、重命名、更改颜色、合并、删除、导入/导出)。

安装

安装插件包:

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

使用

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: [
      /* 视图 */
    ],
    plugins: [sidebarPlugin],
  });

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

属性参数

属性类型默认值描述
widthnumber | string'240px'侧边栏宽度(如 280'20rem'
miniWidthstring'50px'折叠状态下的侧边栏宽度
initialCollapsedbooleanfalse是否以折叠状态启动侧边栏
createCalendarMode'inline' | 'modal''inline'创建日历表单的显示方式
colorPickerMode'blossom' | 'default''default'使用的颜色选择器组件
onSubscribeCalendar(calendar, events) => Promise<void>undefined成功订阅新日历后的回调
onLoadSubscription(calendar) => Promise<void>undefined针对现有订阅的自定义加载逻辑
onReorder(calendars: CalendarType[]) => void | Promise<void>undefined日历排序变更后的回调
render(props: CalendarSidebarRenderProps) => TNodeundefined完全自定义侧边栏 UI
renderSidebarHeader(args: SidebarHeaderSlotArgs) => TNodeundefined自定义侧边栏头部渲染器
renderCalendarContextMenu(calendar, onClose) => TNodeundefined日历项的自定义右键菜单渲染器
renderCreateCalendarDialog(props) => TNodeundefined自定义创建日历对话框渲染器

功能

侧边栏插件提供包含颜色点、可见性开关和折叠按钮的侧栏布局。它会自动与日历状态同步,无需额外代码。同时内置了日历管理与右键菜单功能。

内置功能包括:

  • 迷你日历 - 用于快速日期导航的紧凑月视图
  • 日历列表 - 通过彩色复选框切换各个日历的可见性
  • 创建日历 - 使用自定义名称和颜色添加新日历
  • 重命名 / 更改颜色 - 右键点击日历以重命名或更改颜色
  • 日历排序 - 通过在列表中拖拽日历来调整排序。使用 onReorder 回调将新顺序持久化到后端。
  • 合并日历 - 将一个日历的所有事件移动到另一个日历
  • 删除日历 - 带确认步骤的日历删除(也可先合并)
  • 导入 / 导出 - 导入 .ics 文件或将日历导出为 .ics
  • 订阅日历 - 通过 ICS URL 订阅远程日历,已订阅的日历在侧边栏列表中会显示标识图标

在线示例

这一页用两个示例展示侧边栏的不同形态:一个是开箱即用的默认布局,另一个是使用框架(如 React)完全定制的版本。

可以尝试

  • 可见性切换:在两个示例中切换日历可见性,观察 UI 如何瞬时同步
  • 拖拽同步:拖拽或创建事件,侧栏的"即将到来"卡片会实时更新
  • 收起侧栏:折叠侧栏,看看在周视图等高密度布局里如何回收更多空间

订阅日历

侧边栏内置了订阅远程 ICS 日历源的功能。在侧边栏的日历列表或空白区域右键点击打开菜单,选择订阅日历,然后输入任意公开的 .ics 地址即可。

DayFlow 会自动获取该文件、解析事件并创建一个新日历。已订阅的日历在侧边栏列表中会显示一个小图标,以便与本地创建的日历区分。

自动加载与去重

如果你在初始配置中提供了带有 subscription.url 的日历,侧边栏插件会在挂载时自动获取最新事件。

为了防止将本地缓存的事件与新鲜的订阅数据结合时出现视觉重复,DayFlow 实现了基于 ID 的去重机制

  • 如果来自订阅的事件与核心存储中已有的事件具有相同的 id,订阅版本的事件将优先显示。
  • 这确保了你的 UI 始终显示最新信息,而不会在页面刷新后出现“双重”事件。

持久化订阅日历

DayFlow 通过 CalendarType 对象上是否存在 subscription 字段来识别订阅日历。

订阅日历的默认行为:

  • 只读:UI 修改操作(编辑标题、更换日历、删除)会通过 canMutateFromUI() 禁用。
  • 不可拖拽:禁用拖放操作(移动、调整大小)。
  • 隐藏备注:如果事件没有描述,详情面板中的“备注”字段将被隐藏,以保持界面整洁。

如果您希望允许用户修改订阅事件或移动它们,可以通过在日历对象上设置 readOnly: false 来显式覆盖这些默认保护:

const calendar = useCalendarApp({
  calendars: [
    {
      id: 'team-ics',
      name: '团队日历',
      subscription: {
        url: 'https://example.com/calendar.ics',
        status: 'ready',
      },
      // 覆盖默认保护:
      readOnly: false, // 同时启用 UI 修改和拖放操作
      colors: {
        /* ... */
      },
    },
  ],
});

将日历状态保存到后端时,请保留 subscription 字段,并在下次加载时恢复。 }, ], });


> **注意:** DayFlow 不会自动刷新已订阅的日历源。如需保持事件最新,请在应用中自行实现定期拉取逻辑,并通过 `app.addEvent()` / `app.removeEvent()` 更新事件。

## 完全自定义侧边栏

你可以通过向 `createSidebarPlugin` 传入 `render` 函数来完全替换侧边栏。它会接收实时的日历状态和辅助操作。

```tsx
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'>我的工作区</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'>
        当前日期:{app.getCurrentDate().toLocaleDateString()}
      </div>
    </aside>
  );
};

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

自定义右键菜单

可以替换日历项的默认右键菜单:

createSidebarPlugin({
  renderCalendarContextMenu: (calendar, onClose) => (
    <div className='bg-white shadow-lg border rounded p-2'>
      <button
        onClick={() => {
          console.log('自定义操作');
          onClose();
        }}
      >
        针对 {calendar.name} 的自定义操作
      </button>
    </div>
  ),
});

自定义创建日历对话框

可以替换默认的创建日历对话框:

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

自定义侧边栏头部

可以替换默认的侧边栏头部(包含“日历”标题和折叠按钮的区域):

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

On this page