@dayflow/plugin-sidebar 1.1.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { ICalendarApp, CalendarType, TNode, CreateCalendarDialogProps, CalendarPlugin } from '@dayflow/core';
1
+ import { ICalendarApp, CalendarType, TNode, SidebarHeaderSlotArgs, CreateCalendarDialogProps, CalendarPlugin } from '@dayflow/core';
2
+ export { SidebarHeaderSlotArgs } from '@dayflow/core';
2
3
 
3
4
  interface CalendarSidebarRenderProps {
4
5
  app: ICalendarApp;
@@ -8,6 +9,7 @@ interface CalendarSidebarRenderProps {
8
9
  isCollapsed: boolean;
9
10
  setCollapsed: (collapsed: boolean) => void;
10
11
  renderCalendarContextMenu?: (calendar: CalendarType, onClose: () => void) => TNode;
12
+ renderSidebarHeader?: (args: SidebarHeaderSlotArgs) => TNode;
11
13
  createCalendarMode?: 'inline' | 'modal';
12
14
  renderCreateCalendarDialog?: (props: CreateCalendarDialogProps) => TNode;
13
15
  editingCalendarId?: string | null;
@@ -21,6 +23,7 @@ interface SidebarPluginConfig {
21
23
  createCalendarMode?: 'inline' | 'modal';
22
24
  render?: (props: CalendarSidebarRenderProps) => TNode;
23
25
  renderCalendarContextMenu?: (calendar: CalendarType, onClose: () => void) => TNode;
26
+ renderSidebarHeader?: (args: SidebarHeaderSlotArgs) => TNode;
24
27
  renderCreateCalendarDialog?: (props: CreateCalendarDialogProps) => TNode;
25
28
  [key: string]: unknown;
26
29
  }
package/dist/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useLocale, createPortal, cancelButton, ChevronsUpDown, Check, ChevronRight, sidebarHeader, sidebarHeaderToggle, PanelRightClose, PanelRightOpen, sidebarHeaderTitle, getCalendarColorsForHex, generateUniKey, downloadICS, MiniCalendar, ContextMenu, ContentSlot, ContextMenuLabel, ContextMenuItem, ContextMenuSeparator, ContextMenuColorPicker, DefaultColorPicker, BlossomColorPicker, importICSFile, sidebarContainer, normalizeCssWidth, registerSidebarImplementation, CreateCalendarDialog } from '@dayflow/core';
1
+ import { useLocale, createPortal, cancelButton, ChevronsUpDown, Check, ChevronRight, sidebarHeader, sidebarHeaderToggle, PanelRightClose, PanelRightOpen, sidebarHeaderTitle, getCalendarColorsForHex, generateUniKey, downloadICS, ContentSlot, MiniCalendar, ContextMenu, ContextMenuLabel, ContextMenuItem, ContextMenuSeparator, ContextMenuColorPicker, DefaultColorPicker, BlossomColorPicker, importICSFile, sidebarContainer, normalizeCssWidth, registerSidebarImplementation, CreateCalendarDialog } from '@dayflow/core';
2
2
  import { options, Fragment, h } from 'preact';
3
3
  import { useState, useRef, useCallback, useEffect, useMemo } from 'preact/hooks';
4
4
  import { hexToHsl, lightnessToSliderValue } from '@dayflow/blossom-color-picker';
@@ -177,16 +177,16 @@ const CalendarList = ({ calendars, onToggleVisibility, onReorder, onRename, onCo
177
177
  const isDropTarget = (dropTarget === null || dropTarget === void 0 ? void 0 : dropTarget.id) === calendar.id;
178
178
  const isActive = activeContextMenuCalendarId === calendar.id ||
179
179
  editingId === calendar.id;
180
- return (u("li", { className: 'df-calendar-list-item relative', onDragOver: e => handleDragOver(e, calendar.id), onDragLeave: handleDragLeave, onDrop: () => handleDrop(calendar), onContextMenu: e => onContextMenu(e, calendar.id), children: [isDropTarget && dropTarget.position === 'top' && (u("div", { className: 'pointer-events-none absolute top-0 right-0 left-0 z-10 h-0.5 bg-primary' })), u("div", { draggable: isDraggable && !editingId, onDragStart: e => handleDragStart(calendar, e), onDragEnd: handleDragEnd, className: `rounded transition ${draggedCalendarId === calendar.id ? 'opacity-50' : ''} ${isDraggable ? 'cursor-grab' : 'cursor-default'}`, children: u("div", { className: `group flex items-center rounded px-2 py-2 transition hover:bg-gray-100 dark:hover:bg-slate-800 ${isActive ? 'bg-gray-100 dark:bg-slate-800' : ''}`, title: calendar.name, children: [u("input", { type: 'checkbox', className: 'calendar-checkbox shrink-0 cursor-pointer', style: {
180
+ return (u("li", { className: 'df-calendar-list-item relative', onDragOver: e => handleDragOver(e, calendar.id), onDragLeave: handleDragLeave, onDrop: () => handleDrop(calendar), onContextMenu: e => onContextMenu(e, calendar.id), children: [isDropTarget && dropTarget.position === 'top' && (u("div", { className: 'pointer-events-none absolute top-0 right-0 left-0 z-10 h-0.5 bg-[var(--df-color-primary)]' })), u("div", { draggable: isDraggable && !editingId, onDragStart: e => handleDragStart(calendar, e), onDragEnd: handleDragEnd, className: `rounded transition ${draggedCalendarId === calendar.id ? 'opacity-50' : ''} ${isDraggable ? 'cursor-grab' : 'cursor-default'}`, children: u("div", { className: `group flex items-center rounded px-2 py-2 transition hover:bg-gray-100 dark:hover:bg-slate-800 ${isActive ? 'bg-gray-100 dark:bg-slate-800' : ''}`, title: calendar.name, children: [u("input", { type: 'checkbox', className: 'df-calendar-checkbox shrink-0 cursor-pointer', style: {
181
181
  '--checkbox-color': calendarColor,
182
- }, checked: isVisible, onChange: event => onToggleVisibility(calendar.id, event.target.checked) }), showIcon && (u("span", { className: 'ml-2 flex h-5 w-5 shrink-0 items-center justify-center text-xs font-semibold text-white', "aria-hidden": 'true', children: getCalendarInitials(calendar) })), editingId === calendar.id ? (u("input", { ref: editInputRef, type: 'text', value: editingName, onChange: e => setEditingName(e.target.value), onBlur: handleRenameSave, onKeyDown: handleRenameKeyDown, className: 'ml-2 h-5 min-w-0 flex-1 rounded bg-white px-0 py-0 text-sm text-gray-900 focus:outline-none dark:bg-slate-700 dark:text-gray-100', onClick: e => e.stopPropagation() })) : (u("span", { className: 'ml-2 flex-1 truncate pl-1 text-sm text-gray-700 group-hover:text-gray-900 dark:text-gray-200 dark:group-hover:text-white', onDblClick: () => handleRenameStart(calendar), children: calendar.name || calendar.id }))] }) }), isDropTarget && dropTarget.position === 'bottom' && (u("div", { className: 'pointer-events-none absolute right-0 bottom-0 left-0 z-10 h-0.5 bg-primary' }))] }, calendar.id));
182
+ }, checked: isVisible, onChange: event => onToggleVisibility(calendar.id, event.target.checked) }), showIcon && (u("span", { className: 'ml-2 flex h-5 w-5 shrink-0 items-center justify-center text-xs font-semibold text-white', "aria-hidden": 'true', children: getCalendarInitials(calendar) })), editingId === calendar.id ? (u("input", { ref: editInputRef, type: 'text', value: editingName, onChange: e => setEditingName(e.target.value), onBlur: handleRenameSave, onKeyDown: handleRenameKeyDown, className: 'ml-2 h-5 min-w-0 flex-1 rounded bg-white px-0 py-0 text-sm text-gray-900 focus:outline-none dark:bg-slate-700 dark:text-gray-100', onClick: e => e.stopPropagation() })) : (u("span", { className: 'ml-2 flex-1 truncate pl-1 text-sm text-gray-700 group-hover:text-gray-900 dark:text-gray-200 dark:group-hover:text-white', onDblClick: () => handleRenameStart(calendar), children: calendar.name || calendar.id }))] }) }), isDropTarget && dropTarget.position === 'bottom' && (u("div", { className: 'pointer-events-none absolute right-0 bottom-0 left-0 z-10 h-0.5 bg-[var(--df-color-primary)]' }))] }, calendar.id));
183
183
  }) }) }));
184
184
  };
185
185
 
186
186
  const DeleteCalendarDialog = ({ calendarId, calendarName, calendars, step, onStepChange, onConfirmDelete, onCancel, onMergeSelect, }) => {
187
187
  const [showMergeDropdown, setShowMergeDropdown] = useState(false);
188
188
  const { t } = useLocale();
189
- return createPortal(u("div", { className: 'fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-background p-6 shadow-xl', children: step === 'initial' ? (u(Fragment, { children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('deleteCalendar', { calendarName }) }), u("p", { className: 'mt-3 text-sm text-gray-600 dark:text-gray-300', children: t('deleteCalendarMessage', { calendarName }) }), u("div", { className: 'mt-6 flex items-center justify-between', children: [u("div", { className: 'relative', children: [u("button", { type: 'button', onClick: () => setShowMergeDropdown(!showMergeDropdown), className: 'flex items-center gap-1 rounded-md border border-gray-300 px-4 py-2 text-xs font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-200 dark:hover:bg-slate-700', children: t('merge') }), showMergeDropdown && (u("div", { className: 'absolute top-full left-0 z-10 mt-1 max-h-60 w-max min-w-full overflow-y-auto rounded-md border border-gray-200 bg-background shadow-lg dark:border-slate-700', children: calendars
189
+ return createPortal(u("div", { className: 'df-portal fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-background p-6 shadow-xl', children: step === 'initial' ? (u(Fragment, { children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('deleteCalendar', { calendarName }) }), u("p", { className: 'mt-3 text-sm text-gray-600 dark:text-gray-300', children: t('deleteCalendarMessage', { calendarName }) }), u("div", { className: 'mt-6 flex items-center justify-between', children: [u("div", { className: 'relative', children: [u("button", { type: 'button', onClick: () => setShowMergeDropdown(!showMergeDropdown), className: 'flex items-center gap-1 rounded-md border border-gray-300 px-4 py-2 text-xs font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-200 dark:hover:bg-slate-700', children: t('merge') }), showMergeDropdown && (u("div", { className: 'absolute top-full left-0 z-10 mt-1 max-h-60 w-max min-w-full overflow-y-auto rounded-md border border-gray-200 bg-background shadow-lg dark:border-slate-700', children: calendars
190
190
  .filter(c => c.id !== calendarId)
191
191
  .map(calendar => (u("div", { className: 'flex cursor-pointer items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-slate-700', onClick: () => {
192
192
  onMergeSelect(calendar.id);
@@ -245,7 +245,7 @@ const ImportCalendarDialog = ({ calendars, filename, onConfirm, onCancel, }) =>
245
245
  width: rect.width,
246
246
  }, children: u("div", { className: 'py-1', children: [calendars.map(calendar => (u("div", { className: `flex cursor-pointer items-center px-3 py-2 hover:bg-gray-100 dark:hover:bg-slate-700 ${selectedCalendarId === calendar.id ? 'bg-primary/10' : ''}`, onClick: () => handleSelect(calendar.id), children: [u("div", { className: 'mr-3 h-3 w-3 shrink-0 rounded-sm', style: { backgroundColor: calendar.colors.lineColor } }), u("span", { className: `flex-1 truncate text-sm ${selectedCalendarId === calendar.id ? 'font-medium text-primary' : 'text-gray-700 dark:text-gray-200'}`, children: calendar.name || calendar.id }), selectedCalendarId === calendar.id && (u(Check, { className: 'ml-2 h-4 w-4 shrink-0 text-primary' }))] }, calendar.id))), u("div", { className: 'my-1 border-t border-gray-100 dark:border-slate-700' }), u("div", { className: `flex cursor-pointer items-center px-3 py-2 hover:bg-gray-100 dark:hover:bg-slate-700 ${isNewSelected ? 'bg-primary/10' : ''}`, onClick: () => handleSelect(NEW_CALENDAR_ID), children: [u("span", { className: `flex-1 truncate text-sm ${isNewSelected ? 'font-medium text-primary' : 'pl-6 text-gray-700 dark:text-gray-200'}`, children: [t('newCalendar') || 'New Calendar', ": ", filename] }), isNewSelected && (u(Check, { className: 'ml-2 h-4 w-4 shrink-0 text-primary' }))] })] }) }), document.body);
247
247
  };
248
- return (u("div", { className: 'fixed inset-0 z-100 flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-slate-900', children: [u("h2", { className: 'mb-4 text-lg font-semibold text-gray-900 dark:text-white', children: t('addSchedule') || 'Add Schedule' }), u("p", { className: 'mb-4 text-sm text-gray-600 dark:text-gray-300', children: t('importCalendarMessage') ||
248
+ return (u("div", { className: 'df-portal fixed inset-0 z-100 flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-gray-900', children: [u("h2", { className: 'mb-4 text-lg font-semibold text-gray-900 dark:text-white', children: t('addSchedule') || 'Add Schedule' }), u("p", { className: 'mb-4 text-sm text-gray-600 dark:text-gray-300', children: t('importCalendarMessage') ||
249
249
  'This calendar contains new events. Please select a target calendar.' }), u("div", { className: 'relative', children: [u("button", { ref: triggerRef, type: 'button', className: 'flex w-full items-center rounded-md border border-gray-300 px-3 py-2 shadow-sm transition-colors hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-800', onClick: () => setIsOpen(!isOpen), children: [!isNewSelected && selectedCalendar && (u("div", { className: 'mr-3 h-3 w-3 shrink-0 rounded-sm', style: { backgroundColor: selectedCalendar.colors.lineColor } })), u("span", { className: `flex-1 truncate text-left text-sm font-medium text-gray-700 dark:text-gray-200 ${isNewSelected ? 'pl-0' : ''}`, children: isNewSelected
250
250
  ? `${t('newCalendar')}: ${filename}`
251
251
  : (selectedCalendar === null || selectedCalendar === void 0 ? void 0 : selectedCalendar.name) || (selectedCalendar === null || selectedCalendar === void 0 ? void 0 : selectedCalendar.id) }), u(ChevronsUpDown, { className: 'ml-2 h-4 w-4 shrink-0 text-gray-400' })] }), renderDropdown()] }), u("div", { className: 'mt-8 flex justify-end gap-3', children: [u("button", { type: 'button', onClick: onCancel, className: cancelButton, children: t('cancel') || 'Cancel' }), u("button", { type: 'button', onClick: () => onConfirm(selectedCalendarId), className: 'rounded-md bg-primary px-6 py-2 text-sm font-medium text-primary-foreground shadow-sm transition-colors hover:bg-primary/90', children: t('ok') || 'OK' })] })] }) }));
@@ -290,8 +290,8 @@ const MergeMenuItem = ({ calendars, currentCalendarId, onMergeSelect, }) => {
290
290
  const availableCalendars = calendars.filter(c => c.id !== currentCalendarId);
291
291
  if (availableCalendars.length === 0)
292
292
  return null;
293
- return (u(Fragment, { children: [u("div", { ref: itemRef, className: 'relative flex cursor-default items-center justify-between rounded-sm px-3 py-0.5 text-[12px] transition-colors outline-none select-none hover:bg-primary hover:text-white dark:text-slate-200 dark:hover:bg-primary dark:hover:text-white', onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: [u("span", { children: t('merge') }), u(ChevronRight, { className: 'h-4 w-4' })] }), isHovered &&
294
- createPortal(u("div", { ref: submenuRef, "data-submenu-content": 'true', className: 'animate-in fade-in-0 zoom-in-95 fixed z-60 min-w-48 overflow-hidden rounded-md border border-slate-200 bg-white p-1 shadow-md duration-100 dark:border-slate-800 dark:bg-slate-950', style: { top: position.y, left: position.x }, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseDown: e => e.stopPropagation(), children: availableCalendars.map(calendar => (u("div", { className: 'flex cursor-pointer items-center rounded-sm px-3 py-1 text-[12px] text-slate-900 transition-colors hover:bg-primary hover:text-white dark:text-slate-50 dark:hover:bg-primary dark:hover:text-white', onClick: e => {
293
+ return (u(Fragment, { children: [u("div", { ref: itemRef, className: 'relative flex cursor-default items-center justify-between rounded-sm px-3 py-0.5 text-[12px] text-[var(--df-color-foreground)] transition-colors outline-none select-none hover:bg-[var(--df-color-primary)] hover:text-[var(--df-color-primary-foreground)]', onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: [u("span", { children: t('merge') }), u(ChevronRight, { className: 'h-4 w-4' })] }), isHovered &&
294
+ createPortal(u("div", { ref: submenuRef, "data-submenu-content": 'true', className: 'df-portal df-animate-in df-fade-in df-zoom-in-95 fixed z-60 min-w-48 overflow-hidden rounded-md border border-slate-200 bg-white p-1 shadow-md duration-100 dark:border-slate-800 dark:bg-slate-950', style: { top: position.y, left: position.x }, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseDown: e => e.stopPropagation(), children: availableCalendars.map(calendar => (u("div", { className: 'flex cursor-pointer items-center rounded-sm px-3 py-1 text-[12px] text-[var(--df-color-foreground)] transition-colors hover:bg-[var(--df-color-primary)] hover:text-[var(--df-color-primary-foreground)]', onClick: e => {
295
295
  e.stopPropagation();
296
296
  onMergeSelect(calendar.id);
297
297
  }, children: [u("div", { className: 'mr-2 h-3 w-3 shrink-0 rounded-sm', style: { backgroundColor: calendar.colors.lineColor } }), u("span", { className: 'truncate', children: calendar.name || calendar.id })] }, calendar.id))) }), document.body)] }));
@@ -302,7 +302,7 @@ const SidebarHeader = ({ isCollapsed, onCollapseToggle, }) => {
302
302
  return (u("div", { className: sidebarHeader, children: [u("button", { type: 'button', "aria-label": isCollapsed ? t('expandSidebar') : t('collapseSidebar'), className: sidebarHeaderToggle, onClick: onCollapseToggle, children: isCollapsed ? (u(PanelRightClose, { className: 'h-4 w-4 text-gray-500 dark:text-gray-400' })) : (u(PanelRightOpen, { className: 'h-4 w-4 text-gray-500 dark:text-gray-400' })) }), !isCollapsed && (u("div", { className: 'ml-3 flex flex-1 items-center justify-between', children: u("span", { className: sidebarHeaderTitle, children: t('calendars') }) }))] }));
303
303
  };
304
304
 
305
- const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCollapsed, setCollapsed, renderCalendarContextMenu, editingCalendarId: propEditingCalendarId, setEditingCalendarId: propSetEditingCalendarId, onCreateCalendar, }) => {
305
+ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCollapsed, setCollapsed, renderCalendarContextMenu, renderSidebarHeader, editingCalendarId: propEditingCalendarId, setEditingCalendarId: propSetEditingCalendarId, onCreateCalendar, }) => {
306
306
  var _a, _b, _c, _d;
307
307
  const { t } = useLocale();
308
308
  // Detect if custom color picker slot is provided
@@ -526,7 +526,13 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
526
526
  const readOnlyConfig = app.getReadOnlyConfig();
527
527
  const isEditable = !app.state.readOnly;
528
528
  const isDraggable = readOnlyConfig.draggable !== false;
529
- return (u("div", { className: sidebarContainer, onContextMenu: isEditable ? handleSidebarContextMenu : undefined, children: [u(SidebarHeader, { isCollapsed: isCollapsed, onCollapseToggle: () => setCollapsed(!isCollapsed) }), isCollapsed ? (u(CalendarList, { calendars: calendars, onToggleVisibility: toggleCalendarVisibility, onReorder: isDraggable
529
+ return (u("div", { className: sidebarContainer, onContextMenu: isEditable ? handleSidebarContextMenu : undefined, children: [u(ContentSlot, { generatorName: 'sidebarHeader', generatorArgs: {
530
+ isCollapsed,
531
+ onCollapseToggle: () => setCollapsed(!isCollapsed),
532
+ }, defaultContent: renderSidebarHeader ? (renderSidebarHeader({
533
+ isCollapsed,
534
+ onCollapseToggle: () => setCollapsed(!isCollapsed),
535
+ })) : (u(SidebarHeader, { isCollapsed: isCollapsed, onCollapseToggle: () => setCollapsed(!isCollapsed) })) }), isCollapsed ? (u(CalendarList, { calendars: calendars, onToggleVisibility: toggleCalendarVisibility, onReorder: isDraggable
530
536
  ? app.reorderCalendars
531
537
  : () => {
532
538
  /* noop */
@@ -589,7 +595,7 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
589
595
  darkColors,
590
596
  });
591
597
  },
592
- }, defaultContent: u("div", { className: 'rounded-lg border border-gray-200 bg-white p-3 shadow-xl dark:border-gray-700 dark:bg-slate-900', children: u(DefaultColorPicker, { color: customColorPicker.currentColor, onChange: (color, isPending) => {
598
+ }, defaultContent: u("div", { className: 'rounded-lg border border-gray-200 bg-white p-3 shadow-xl dark:border-gray-700 dark:bg-gray-900', children: u(DefaultColorPicker, { color: customColorPicker.currentColor, onChange: (color, isPending) => {
593
599
  setCustomColorPicker(prev => prev ? Object.assign(Object.assign({}, prev), { currentColor: color.hex }) : null);
594
600
  const { colors, darkColors } = getCalendarColorsForHex(color.hex);
595
601
  app.updateCalendar(customColorPicker.calendarId, {
@@ -678,6 +684,7 @@ function createSidebarPlugin(config = {}) {
678
684
  isCollapsed,
679
685
  setCollapsed: setIsCollapsed,
680
686
  renderCalendarContextMenu: config.renderCalendarContextMenu,
687
+ renderSidebarHeader: config.renderSidebarHeader,
681
688
  createCalendarMode: config.createCalendarMode,
682
689
  renderCreateCalendarDialog: config.renderCreateCalendarDialog,
683
690
  editingCalendarId,
package/dist/index.js CHANGED
@@ -179,16 +179,16 @@ const CalendarList = ({ calendars, onToggleVisibility, onReorder, onRename, onCo
179
179
  const isDropTarget = (dropTarget === null || dropTarget === void 0 ? void 0 : dropTarget.id) === calendar.id;
180
180
  const isActive = activeContextMenuCalendarId === calendar.id ||
181
181
  editingId === calendar.id;
182
- return (u("li", { className: 'df-calendar-list-item relative', onDragOver: e => handleDragOver(e, calendar.id), onDragLeave: handleDragLeave, onDrop: () => handleDrop(calendar), onContextMenu: e => onContextMenu(e, calendar.id), children: [isDropTarget && dropTarget.position === 'top' && (u("div", { className: 'pointer-events-none absolute top-0 right-0 left-0 z-10 h-0.5 bg-primary' })), u("div", { draggable: isDraggable && !editingId, onDragStart: e => handleDragStart(calendar, e), onDragEnd: handleDragEnd, className: `rounded transition ${draggedCalendarId === calendar.id ? 'opacity-50' : ''} ${isDraggable ? 'cursor-grab' : 'cursor-default'}`, children: u("div", { className: `group flex items-center rounded px-2 py-2 transition hover:bg-gray-100 dark:hover:bg-slate-800 ${isActive ? 'bg-gray-100 dark:bg-slate-800' : ''}`, title: calendar.name, children: [u("input", { type: 'checkbox', className: 'calendar-checkbox shrink-0 cursor-pointer', style: {
182
+ return (u("li", { className: 'df-calendar-list-item relative', onDragOver: e => handleDragOver(e, calendar.id), onDragLeave: handleDragLeave, onDrop: () => handleDrop(calendar), onContextMenu: e => onContextMenu(e, calendar.id), children: [isDropTarget && dropTarget.position === 'top' && (u("div", { className: 'pointer-events-none absolute top-0 right-0 left-0 z-10 h-0.5 bg-[var(--df-color-primary)]' })), u("div", { draggable: isDraggable && !editingId, onDragStart: e => handleDragStart(calendar, e), onDragEnd: handleDragEnd, className: `rounded transition ${draggedCalendarId === calendar.id ? 'opacity-50' : ''} ${isDraggable ? 'cursor-grab' : 'cursor-default'}`, children: u("div", { className: `group flex items-center rounded px-2 py-2 transition hover:bg-gray-100 dark:hover:bg-slate-800 ${isActive ? 'bg-gray-100 dark:bg-slate-800' : ''}`, title: calendar.name, children: [u("input", { type: 'checkbox', className: 'df-calendar-checkbox shrink-0 cursor-pointer', style: {
183
183
  '--checkbox-color': calendarColor,
184
- }, checked: isVisible, onChange: event => onToggleVisibility(calendar.id, event.target.checked) }), showIcon && (u("span", { className: 'ml-2 flex h-5 w-5 shrink-0 items-center justify-center text-xs font-semibold text-white', "aria-hidden": 'true', children: getCalendarInitials(calendar) })), editingId === calendar.id ? (u("input", { ref: editInputRef, type: 'text', value: editingName, onChange: e => setEditingName(e.target.value), onBlur: handleRenameSave, onKeyDown: handleRenameKeyDown, className: 'ml-2 h-5 min-w-0 flex-1 rounded bg-white px-0 py-0 text-sm text-gray-900 focus:outline-none dark:bg-slate-700 dark:text-gray-100', onClick: e => e.stopPropagation() })) : (u("span", { className: 'ml-2 flex-1 truncate pl-1 text-sm text-gray-700 group-hover:text-gray-900 dark:text-gray-200 dark:group-hover:text-white', onDblClick: () => handleRenameStart(calendar), children: calendar.name || calendar.id }))] }) }), isDropTarget && dropTarget.position === 'bottom' && (u("div", { className: 'pointer-events-none absolute right-0 bottom-0 left-0 z-10 h-0.5 bg-primary' }))] }, calendar.id));
184
+ }, checked: isVisible, onChange: event => onToggleVisibility(calendar.id, event.target.checked) }), showIcon && (u("span", { className: 'ml-2 flex h-5 w-5 shrink-0 items-center justify-center text-xs font-semibold text-white', "aria-hidden": 'true', children: getCalendarInitials(calendar) })), editingId === calendar.id ? (u("input", { ref: editInputRef, type: 'text', value: editingName, onChange: e => setEditingName(e.target.value), onBlur: handleRenameSave, onKeyDown: handleRenameKeyDown, className: 'ml-2 h-5 min-w-0 flex-1 rounded bg-white px-0 py-0 text-sm text-gray-900 focus:outline-none dark:bg-slate-700 dark:text-gray-100', onClick: e => e.stopPropagation() })) : (u("span", { className: 'ml-2 flex-1 truncate pl-1 text-sm text-gray-700 group-hover:text-gray-900 dark:text-gray-200 dark:group-hover:text-white', onDblClick: () => handleRenameStart(calendar), children: calendar.name || calendar.id }))] }) }), isDropTarget && dropTarget.position === 'bottom' && (u("div", { className: 'pointer-events-none absolute right-0 bottom-0 left-0 z-10 h-0.5 bg-[var(--df-color-primary)]' }))] }, calendar.id));
185
185
  }) }) }));
186
186
  };
187
187
 
188
188
  const DeleteCalendarDialog = ({ calendarId, calendarName, calendars, step, onStepChange, onConfirmDelete, onCancel, onMergeSelect, }) => {
189
189
  const [showMergeDropdown, setShowMergeDropdown] = hooks.useState(false);
190
190
  const { t } = core.useLocale();
191
- return core.createPortal(u("div", { className: 'fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-background p-6 shadow-xl', children: step === 'initial' ? (u(preact.Fragment, { children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('deleteCalendar', { calendarName }) }), u("p", { className: 'mt-3 text-sm text-gray-600 dark:text-gray-300', children: t('deleteCalendarMessage', { calendarName }) }), u("div", { className: 'mt-6 flex items-center justify-between', children: [u("div", { className: 'relative', children: [u("button", { type: 'button', onClick: () => setShowMergeDropdown(!showMergeDropdown), className: 'flex items-center gap-1 rounded-md border border-gray-300 px-4 py-2 text-xs font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-200 dark:hover:bg-slate-700', children: t('merge') }), showMergeDropdown && (u("div", { className: 'absolute top-full left-0 z-10 mt-1 max-h-60 w-max min-w-full overflow-y-auto rounded-md border border-gray-200 bg-background shadow-lg dark:border-slate-700', children: calendars
191
+ return core.createPortal(u("div", { className: 'df-portal fixed inset-0 z-[9999] flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-background p-6 shadow-xl', children: step === 'initial' ? (u(preact.Fragment, { children: [u("h2", { className: 'text-lg font-semibold text-gray-900 dark:text-white', children: t('deleteCalendar', { calendarName }) }), u("p", { className: 'mt-3 text-sm text-gray-600 dark:text-gray-300', children: t('deleteCalendarMessage', { calendarName }) }), u("div", { className: 'mt-6 flex items-center justify-between', children: [u("div", { className: 'relative', children: [u("button", { type: 'button', onClick: () => setShowMergeDropdown(!showMergeDropdown), className: 'flex items-center gap-1 rounded-md border border-gray-300 px-4 py-2 text-xs font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-200 dark:hover:bg-slate-700', children: t('merge') }), showMergeDropdown && (u("div", { className: 'absolute top-full left-0 z-10 mt-1 max-h-60 w-max min-w-full overflow-y-auto rounded-md border border-gray-200 bg-background shadow-lg dark:border-slate-700', children: calendars
192
192
  .filter(c => c.id !== calendarId)
193
193
  .map(calendar => (u("div", { className: 'flex cursor-pointer items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-slate-700', onClick: () => {
194
194
  onMergeSelect(calendar.id);
@@ -247,7 +247,7 @@ const ImportCalendarDialog = ({ calendars, filename, onConfirm, onCancel, }) =>
247
247
  width: rect.width,
248
248
  }, children: u("div", { className: 'py-1', children: [calendars.map(calendar => (u("div", { className: `flex cursor-pointer items-center px-3 py-2 hover:bg-gray-100 dark:hover:bg-slate-700 ${selectedCalendarId === calendar.id ? 'bg-primary/10' : ''}`, onClick: () => handleSelect(calendar.id), children: [u("div", { className: 'mr-3 h-3 w-3 shrink-0 rounded-sm', style: { backgroundColor: calendar.colors.lineColor } }), u("span", { className: `flex-1 truncate text-sm ${selectedCalendarId === calendar.id ? 'font-medium text-primary' : 'text-gray-700 dark:text-gray-200'}`, children: calendar.name || calendar.id }), selectedCalendarId === calendar.id && (u(core.Check, { className: 'ml-2 h-4 w-4 shrink-0 text-primary' }))] }, calendar.id))), u("div", { className: 'my-1 border-t border-gray-100 dark:border-slate-700' }), u("div", { className: `flex cursor-pointer items-center px-3 py-2 hover:bg-gray-100 dark:hover:bg-slate-700 ${isNewSelected ? 'bg-primary/10' : ''}`, onClick: () => handleSelect(NEW_CALENDAR_ID), children: [u("span", { className: `flex-1 truncate text-sm ${isNewSelected ? 'font-medium text-primary' : 'pl-6 text-gray-700 dark:text-gray-200'}`, children: [t('newCalendar') || 'New Calendar', ": ", filename] }), isNewSelected && (u(core.Check, { className: 'ml-2 h-4 w-4 shrink-0 text-primary' }))] })] }) }), document.body);
249
249
  };
250
- return (u("div", { className: 'fixed inset-0 z-100 flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-slate-900', children: [u("h2", { className: 'mb-4 text-lg font-semibold text-gray-900 dark:text-white', children: t('addSchedule') || 'Add Schedule' }), u("p", { className: 'mb-4 text-sm text-gray-600 dark:text-gray-300', children: t('importCalendarMessage') ||
250
+ return (u("div", { className: 'df-portal fixed inset-0 z-100 flex items-center justify-center bg-black/50', children: u("div", { className: 'w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-gray-900', children: [u("h2", { className: 'mb-4 text-lg font-semibold text-gray-900 dark:text-white', children: t('addSchedule') || 'Add Schedule' }), u("p", { className: 'mb-4 text-sm text-gray-600 dark:text-gray-300', children: t('importCalendarMessage') ||
251
251
  'This calendar contains new events. Please select a target calendar.' }), u("div", { className: 'relative', children: [u("button", { ref: triggerRef, type: 'button', className: 'flex w-full items-center rounded-md border border-gray-300 px-3 py-2 shadow-sm transition-colors hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-800', onClick: () => setIsOpen(!isOpen), children: [!isNewSelected && selectedCalendar && (u("div", { className: 'mr-3 h-3 w-3 shrink-0 rounded-sm', style: { backgroundColor: selectedCalendar.colors.lineColor } })), u("span", { className: `flex-1 truncate text-left text-sm font-medium text-gray-700 dark:text-gray-200 ${isNewSelected ? 'pl-0' : ''}`, children: isNewSelected
252
252
  ? `${t('newCalendar')}: ${filename}`
253
253
  : (selectedCalendar === null || selectedCalendar === void 0 ? void 0 : selectedCalendar.name) || (selectedCalendar === null || selectedCalendar === void 0 ? void 0 : selectedCalendar.id) }), u(core.ChevronsUpDown, { className: 'ml-2 h-4 w-4 shrink-0 text-gray-400' })] }), renderDropdown()] }), u("div", { className: 'mt-8 flex justify-end gap-3', children: [u("button", { type: 'button', onClick: onCancel, className: core.cancelButton, children: t('cancel') || 'Cancel' }), u("button", { type: 'button', onClick: () => onConfirm(selectedCalendarId), className: 'rounded-md bg-primary px-6 py-2 text-sm font-medium text-primary-foreground shadow-sm transition-colors hover:bg-primary/90', children: t('ok') || 'OK' })] })] }) }));
@@ -292,8 +292,8 @@ const MergeMenuItem = ({ calendars, currentCalendarId, onMergeSelect, }) => {
292
292
  const availableCalendars = calendars.filter(c => c.id !== currentCalendarId);
293
293
  if (availableCalendars.length === 0)
294
294
  return null;
295
- return (u(preact.Fragment, { children: [u("div", { ref: itemRef, className: 'relative flex cursor-default items-center justify-between rounded-sm px-3 py-0.5 text-[12px] transition-colors outline-none select-none hover:bg-primary hover:text-white dark:text-slate-200 dark:hover:bg-primary dark:hover:text-white', onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: [u("span", { children: t('merge') }), u(core.ChevronRight, { className: 'h-4 w-4' })] }), isHovered &&
296
- core.createPortal(u("div", { ref: submenuRef, "data-submenu-content": 'true', className: 'animate-in fade-in-0 zoom-in-95 fixed z-60 min-w-48 overflow-hidden rounded-md border border-slate-200 bg-white p-1 shadow-md duration-100 dark:border-slate-800 dark:bg-slate-950', style: { top: position.y, left: position.x }, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseDown: e => e.stopPropagation(), children: availableCalendars.map(calendar => (u("div", { className: 'flex cursor-pointer items-center rounded-sm px-3 py-1 text-[12px] text-slate-900 transition-colors hover:bg-primary hover:text-white dark:text-slate-50 dark:hover:bg-primary dark:hover:text-white', onClick: e => {
295
+ return (u(preact.Fragment, { children: [u("div", { ref: itemRef, className: 'relative flex cursor-default items-center justify-between rounded-sm px-3 py-0.5 text-[12px] text-[var(--df-color-foreground)] transition-colors outline-none select-none hover:bg-[var(--df-color-primary)] hover:text-[var(--df-color-primary-foreground)]', onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: [u("span", { children: t('merge') }), u(core.ChevronRight, { className: 'h-4 w-4' })] }), isHovered &&
296
+ core.createPortal(u("div", { ref: submenuRef, "data-submenu-content": 'true', className: 'df-portal df-animate-in df-fade-in df-zoom-in-95 fixed z-60 min-w-48 overflow-hidden rounded-md border border-slate-200 bg-white p-1 shadow-md duration-100 dark:border-slate-800 dark:bg-slate-950', style: { top: position.y, left: position.x }, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseDown: e => e.stopPropagation(), children: availableCalendars.map(calendar => (u("div", { className: 'flex cursor-pointer items-center rounded-sm px-3 py-1 text-[12px] text-[var(--df-color-foreground)] transition-colors hover:bg-[var(--df-color-primary)] hover:text-[var(--df-color-primary-foreground)]', onClick: e => {
297
297
  e.stopPropagation();
298
298
  onMergeSelect(calendar.id);
299
299
  }, children: [u("div", { className: 'mr-2 h-3 w-3 shrink-0 rounded-sm', style: { backgroundColor: calendar.colors.lineColor } }), u("span", { className: 'truncate', children: calendar.name || calendar.id })] }, calendar.id))) }), document.body)] }));
@@ -304,7 +304,7 @@ const SidebarHeader = ({ isCollapsed, onCollapseToggle, }) => {
304
304
  return (u("div", { className: core.sidebarHeader, children: [u("button", { type: 'button', "aria-label": isCollapsed ? t('expandSidebar') : t('collapseSidebar'), className: core.sidebarHeaderToggle, onClick: onCollapseToggle, children: isCollapsed ? (u(core.PanelRightClose, { className: 'h-4 w-4 text-gray-500 dark:text-gray-400' })) : (u(core.PanelRightOpen, { className: 'h-4 w-4 text-gray-500 dark:text-gray-400' })) }), !isCollapsed && (u("div", { className: 'ml-3 flex flex-1 items-center justify-between', children: u("span", { className: core.sidebarHeaderTitle, children: t('calendars') }) }))] }));
305
305
  };
306
306
 
307
- const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCollapsed, setCollapsed, renderCalendarContextMenu, editingCalendarId: propEditingCalendarId, setEditingCalendarId: propSetEditingCalendarId, onCreateCalendar, }) => {
307
+ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCollapsed, setCollapsed, renderCalendarContextMenu, renderSidebarHeader, editingCalendarId: propEditingCalendarId, setEditingCalendarId: propSetEditingCalendarId, onCreateCalendar, }) => {
308
308
  var _a, _b, _c, _d;
309
309
  const { t } = core.useLocale();
310
310
  // Detect if custom color picker slot is provided
@@ -528,7 +528,13 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
528
528
  const readOnlyConfig = app.getReadOnlyConfig();
529
529
  const isEditable = !app.state.readOnly;
530
530
  const isDraggable = readOnlyConfig.draggable !== false;
531
- return (u("div", { className: core.sidebarContainer, onContextMenu: isEditable ? handleSidebarContextMenu : undefined, children: [u(SidebarHeader, { isCollapsed: isCollapsed, onCollapseToggle: () => setCollapsed(!isCollapsed) }), isCollapsed ? (u(CalendarList, { calendars: calendars, onToggleVisibility: toggleCalendarVisibility, onReorder: isDraggable
531
+ return (u("div", { className: core.sidebarContainer, onContextMenu: isEditable ? handleSidebarContextMenu : undefined, children: [u(core.ContentSlot, { generatorName: 'sidebarHeader', generatorArgs: {
532
+ isCollapsed,
533
+ onCollapseToggle: () => setCollapsed(!isCollapsed),
534
+ }, defaultContent: renderSidebarHeader ? (renderSidebarHeader({
535
+ isCollapsed,
536
+ onCollapseToggle: () => setCollapsed(!isCollapsed),
537
+ })) : (u(SidebarHeader, { isCollapsed: isCollapsed, onCollapseToggle: () => setCollapsed(!isCollapsed) })) }), isCollapsed ? (u(CalendarList, { calendars: calendars, onToggleVisibility: toggleCalendarVisibility, onReorder: isDraggable
532
538
  ? app.reorderCalendars
533
539
  : () => {
534
540
  /* noop */
@@ -591,7 +597,7 @@ const DefaultCalendarSidebar = ({ app, calendars, toggleCalendarVisibility, isCo
591
597
  darkColors,
592
598
  });
593
599
  },
594
- }, defaultContent: u("div", { className: 'rounded-lg border border-gray-200 bg-white p-3 shadow-xl dark:border-gray-700 dark:bg-slate-900', children: u(core.DefaultColorPicker, { color: customColorPicker.currentColor, onChange: (color, isPending) => {
600
+ }, defaultContent: u("div", { className: 'rounded-lg border border-gray-200 bg-white p-3 shadow-xl dark:border-gray-700 dark:bg-gray-900', children: u(core.DefaultColorPicker, { color: customColorPicker.currentColor, onChange: (color, isPending) => {
595
601
  setCustomColorPicker(prev => prev ? Object.assign(Object.assign({}, prev), { currentColor: color.hex }) : null);
596
602
  const { colors, darkColors } = core.getCalendarColorsForHex(color.hex);
597
603
  app.updateCalendar(customColorPicker.calendarId, {
@@ -680,6 +686,7 @@ function createSidebarPlugin(config = {}) {
680
686
  isCollapsed,
681
687
  setCollapsed: setIsCollapsed,
682
688
  renderCalendarContextMenu: config.renderCalendarContextMenu,
689
+ renderSidebarHeader: config.renderSidebarHeader,
683
690
  createCalendarMode: config.createCalendarMode,
684
691
  renderCreateCalendarDialog: config.renderCreateCalendarDialog,
685
692
  editingCalendarId,
@@ -1,4 +1,4 @@
1
1
  import { JSX } from 'preact';
2
2
  import type { CalendarSidebarRenderProps } from './plugin';
3
- declare const DefaultCalendarSidebar: ({ app, calendars, toggleCalendarVisibility, isCollapsed, setCollapsed, renderCalendarContextMenu, editingCalendarId: propEditingCalendarId, setEditingCalendarId: propSetEditingCalendarId, onCreateCalendar, }: CalendarSidebarRenderProps) => JSX.Element;
3
+ declare const DefaultCalendarSidebar: ({ app, calendars, toggleCalendarVisibility, isCollapsed, setCollapsed, renderCalendarContextMenu, renderSidebarHeader, editingCalendarId: propEditingCalendarId, setEditingCalendarId: propSetEditingCalendarId, onCreateCalendar, }: CalendarSidebarRenderProps) => JSX.Element;
4
4
  export default DefaultCalendarSidebar;
@@ -1,4 +1,5 @@
1
- import { CalendarPlugin, ICalendarApp, CalendarType, TNode, CreateCalendarDialogProps } from '@dayflow/core';
1
+ import { CalendarPlugin, ICalendarApp, CalendarType, TNode, CreateCalendarDialogProps, SidebarHeaderSlotArgs } from '@dayflow/core';
2
+ export type { SidebarHeaderSlotArgs };
2
3
  export interface CalendarSidebarRenderProps {
3
4
  app: ICalendarApp;
4
5
  calendars: CalendarType[];
@@ -7,6 +8,7 @@ export interface CalendarSidebarRenderProps {
7
8
  isCollapsed: boolean;
8
9
  setCollapsed: (collapsed: boolean) => void;
9
10
  renderCalendarContextMenu?: (calendar: CalendarType, onClose: () => void) => TNode;
11
+ renderSidebarHeader?: (args: SidebarHeaderSlotArgs) => TNode;
10
12
  createCalendarMode?: 'inline' | 'modal';
11
13
  renderCreateCalendarDialog?: (props: CreateCalendarDialogProps) => TNode;
12
14
  editingCalendarId?: string | null;
@@ -20,6 +22,7 @@ export interface SidebarPluginConfig {
20
22
  createCalendarMode?: 'inline' | 'modal';
21
23
  render?: (props: CalendarSidebarRenderProps) => TNode;
22
24
  renderCalendarContextMenu?: (calendar: CalendarType, onClose: () => void) => TNode;
25
+ renderSidebarHeader?: (args: SidebarHeaderSlotArgs) => TNode;
23
26
  renderCreateCalendarDialog?: (props: CreateCalendarDialogProps) => TNode;
24
27
  [key: string]: unknown;
25
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dayflow/plugin-sidebar",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "Sidebar plugin for DayFlow calendar",
5
5
  "keywords": [
6
6
  "calendar",
@@ -37,10 +37,10 @@
37
37
  "rollup-plugin-dts": "^6.3.0",
38
38
  "temporal-polyfill": "^0.3.0",
39
39
  "typescript": "^5.9.3",
40
- "@dayflow/core": "3.2.3"
40
+ "@dayflow/core": "3.3.0"
41
41
  },
42
42
  "peerDependencies": {
43
- "@dayflow/core": "3.2.3"
43
+ "@dayflow/core": "3.3.0"
44
44
  },
45
45
  "scripts": {
46
46
  "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && rollup -c",