Content Slots

DayFlow verwendet eine slotbasierte Architektur: Damit setzen Sie eigene UI-Komponenten aus Ihrem bevorzugten Framework (React, Vue usw.) direkt in die Kalender-Engine ein.

Möglich macht das der ContentSlot-Mechanismus. Die Engine liefert für die meisten UI-Elemente Standardimplementierungen (in Preact) mit, die Sie überschreiben können – passend zum Design Ihrer Anwendung oder zu bestimmten Bibliotheken.

Inhaltsübersicht

Funktionsweise

  1. Der Kern definiert einen Slot: In @dayflow/core sind bestimmte UI-Bereiche in einen ContentSlot gehüllt. Jeder Slot hat einen generatorName und generatorArgs.
  2. Sie liefern die Implementierung: Sie übergeben eine Komponente oder Render-Funktion an DayFlowCalendar. Der Adapter portalt Ihre Komponente dann genau an die vom Kern definierte Stelle.

Verfügbare Slots

Generator-NameBeschreibungArgumente
colorPickerEin kleiner Farbwähler für Terminfarben.{ color, onChange, onChangeComplete }
createCalendarDialogColorPickerEin vollwertiger Farbwähler für Dialoge.{ color, onChange, onAccept, onCancel, styles }
eventContent*Eigenes Rendering der Terminkarten (etwa eventContentDay).{ event, viewType, isAllDay, isMobile, isSelected, isDragging, layout }
eventContextMenuEigenes Kontextmenü für Termine.{ event, onClose }
eventDetailContentDer Inhalt, der im Popover bzw. Panel erscheint, wenn Termindetails geöffnet werden.{ event, isAllDay, onEventDelete, onEventUpdate, onClose, app }
eventDetailDialogEigener Dialog beim Öffnen der Termindetails. Erfordert useEventDetailDialog: true in useCalendarApp.{ event, isOpen, isAllDay, onEventDelete, onEventUpdate, onClose, app }
gridContextMenuEigenes Kontextmenü für Kalenderzellen bzw. das Raster.{ date, viewType, onClose }
gridPopupContentEigener Popup-Inhalt im grid-Modus der Jahresansicht (bei gridDateClick: 'popup').{ date, events }
mobileEventDetailEigener Detailbereich bzw. Dialog für Termine auf Mobilgeräten.{ isOpen, onClose, onSave, onEventDelete, draftEvent, app, timeFormat }
sidebarCalendarColorPickerFarbwähler für Kalenderfarben in der Seitenleiste.{ color, onChange, onChangeComplete }
titleBarSlotZusätzlicher Inhalt in der Titelleiste der Seitenleiste.{ isCollapsed, toggleCollapsed }

Termininhalt

Mit den eventContent-Slots gestalten Sie vollständig, wie Termine im Kalender dargestellt werden. Anders als bei den übrigen Slots muss der Termininhalt je Ansicht angegeben werden, damit das Layout zur jeweiligen Terminstruktur passt.

Slots je Ansicht

Slot-NameBeschreibung
eventContentDayÜberschreibt Termine mit Uhrzeit in der Tagesansicht.
eventContentWeekÜberschreibt Termine mit Uhrzeit in der Wochenansicht.
eventContentMonthÜberschreibt Termine in der Monatsansicht.
eventContentYearÜberschreibt Termine in der Jahresansicht.
eventContentAllDayDayÜberschreibt ganztägige Termine in der Tagesansicht.
eventContentAllDayWeekÜberschreibt ganztägige Termine in der Wochenansicht.
eventContentAllDayMonthÜberschreibt ganztägige Termine in der Monatsansicht.
eventContentAllDayYearÜberschreibt ganztägige Termine in der Jahresansicht.
Quellcode ansehen
import { DayFlowCalendar } from '@dayflow/react';

export const EventContentShowcase = () => {
  return (
    <DayFlowCalendar
      calendar={calendar}
      // Customize event rendering for Day View
      eventContentDay={({ event, isSelected }) => (
        <div className='custom-event-card'>
          <span>{event.title}</span>
          {/* ... add your custom icons or layout */}
        </div>
      )}
      // Customize event rendering for Month View
      eventContentMonth={({ event }) => (
        <div className='flex items-center gap-1'>
          <span>🗓️</span>
          <span className='truncate'>{event.title}</span>
        </div>
      )}
      // Omit other view overrides for brevity...
    />
  );
};
<template>
  <DayFlowCalendar :calendar="calendar">
    <!-- Customize event rendering for Day View -->
    <template #eventContentDay="{ event, isSelected }">
      <div class="custom-event-card">
        <span>{{ event.title }}</span>
      </div>
    </template>

    <!-- Customize event rendering for Month View -->
    <template #eventContentMonth="{ event }">
      <div class="flex items-center gap-1">
        <span>🗓️</span>
        <span class="truncate">{{ event.title }}</span>
      </div>
    </template>
  </DayFlowCalendar>
</template>

<script setup>
import { DayFlowCalendar } from '@dayflow/vue';
</script>
<dayflow-calendar
  [calendar]="calendar"
  [eventContentDay]="dayContent"
  [eventContentMonth]="monthContent"
>
</dayflow-calendar>

<ng-template #dayContent let-args>
  <div class="custom-event-card">
    <span>{{ args.event.title }}</span>
  </div>
</ng-template>

<ng-template #monthContent let-args>
  <div class="flex items-center gap-1">
    <span>🗓️</span>
    <span class="truncate">{{ args.event.title }}</span>
  </div>
</ng-template>
<DayFlowCalendar
  {calendar}
  eventContentDay={CustomDayContent}
  eventContentMonth={CustomMonthContent}
/>

<!-- CustomDayContent.svelte -->
<script lang="ts">
  import type { EventContentSlotArgs } from '@dayflow/core';
  let { event } = $props<EventContentSlotArgs>();
</script>
<div class="custom-event-card">
  <span>{event.title}</span>
</div>

<!-- CustomMonthContent.svelte -->
<script lang="ts">
  import type { EventContentSlotArgs } from '@dayflow/core';
  let { event } = $props<EventContentSlotArgs>();
</script>
<div class="flex items-center gap-1">
  <span>🗓️</span>
  <span class="truncate">{event.title}</span>
</div>

Inhalt der Termindetails

Mit dem Slot eventDetailContent passen Sie den Inhalt an, der beim Öffnen der Termindetails im Popover oder Panel erscheint.

Quellcode ansehen
import { DayFlowCalendar } from '@dayflow/react';
import { useCallback } from 'react';

export const CustomDetailPanelShowcase = () => {
  const detailPanel = useCallback(
    ({ event, onEventDelete, onEventUpdate, onClose }) => {
      return (
        <div className='p-4 space-y-3'>
          <h5 className='font-bold'>{event.title}</h5>
          <p>{event.description}</p>

          <div className='flex gap-2'>
            <button
              onClick={() => onEventUpdate({ ...event, title: 'Updated' })}
            >
              Update
            </button>
            <button onClick={() => onEventDelete(event.id)}>Delete</button>
          </div>
        </div>
      );
    },
    []
  );

  return (
    <DayFlowCalendar calendar={calendar} eventDetailContent={detailPanel} />
  );
};
<template>
  <DayFlowCalendar :calendar="calendar">
    <template #eventDetailContent="{ event, onEventDelete, onEventUpdate, onClose }">
      <div class="p-4 space-y-3">
        <h5 class="font-bold">{{ event.title }}</h5>
        <p>{{ event.description }}</p>

        <div class="flex gap-2">
          <button @click="onEventUpdate({ ...event, title: 'Updated' })">
            Update
          </button>
          <button @click="onEventDelete(event.id)">Delete</button>
        </div>
      </div>
    </template>
  </DayFlowCalendar>
</template>

<script setup>
import { DayFlowCalendar } from '@dayflow/vue';
</script>
<dayflow-calendar
  [calendar]="calendar"
  [eventDetailContent]="detailPanel"
>
</dayflow-calendar>

<ng-template #detailPanel let-args>
  <div class="p-4 space-y-3">
    <h5 class="font-bold">{{ args.event.title }}</h5>
    <p>{{ args.event.description }}</p>

    <div class="flex gap-2">
      <button (click)="args.onEventUpdate({ ...args.event, title: 'Updated' })">
        Update
      </button>
      <button (click)="args.onEventDelete(args.event.id)">Delete</button>
    </div>
  </div>
</ng-template>
<DayFlowCalendar {calendar} eventDetailContent={CustomDetailPanel} />

<!-- CustomDetailPanel.svelte -->
<script lang="ts">
  import type { EventDetailContentSlotArgs } from '@dayflow/core';
  let { event, onEventDelete, onEventUpdate } = $props<EventDetailContentSlotArgs>();
</script>

<div class="p-4 space-y-3">
  <h5 class="font-bold">{event.title}</h5>
  <p>{event.description}</p>

  <div class="flex gap-2">
    <button onclick={() => onEventUpdate({ ...event, title: 'Updated' })}>
      Update
    </button>
    <button onclick={() => onEventDelete(event.id)}>Delete</button>
  </div>
</div>

Termindetail-Dialog

Wenn Sie Termindetails lieber in einem Modal ansehen und bearbeiten, verwenden Sie den Slot eventDetailDialog. Das ist besonders bei Mobile-First-Anwendungen nützlich oder wenn Sie mehr Platz für komplexe Termindaten brauchen.

Voraussetzung: Setzen Sie useEventDetailDialog: true in useCalendarApp, um den Dialogmodus zu aktivieren. Ohne das wird der Slot nie ausgelöst.

Stattdessen abschalten: Wenn Sie das eingebaute schwebende Panel unterdrücken wollen, ohne es durch einen eigenen Dialog zu ersetzen, setzen Sie in Ihrer useCalendarApp-Konfiguration useEventDetailPanel: false, statt einen Slot zu verwenden.

Quellcode ansehen
import { DayFlowCalendar } from '@dayflow/react';
import { useCallback } from 'react';

export const CustomDetailDialogShowcase = () => {
  const customDialog = useCallback(({ event, isOpen, onClose }) => {
    if (!isOpen) return null;

    return (
      <div className='fixed inset-0 z-50 flex items-center justify-center bg-black/50'>
        <div className='bg-white p-6 rounded-lg shadow-xl'>
          <h2>{event.title}</h2>
          {/* ... build your custom dialog UI here */}
          <button onClick={onClose}>Close</button>
        </div>
      </div>
    );
  }, []);

  return (
    <DayFlowCalendar calendar={calendar} eventDetailDialog={customDialog} />
  );
};
<template>
  <DayFlowCalendar :calendar="calendar">
    <template #eventDetailDialog="{ event, isOpen, onClose }">
      <div v-if="isOpen" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
        <div class="bg-white p-6 rounded-lg shadow-xl">
          <h2>{{ event.title }}</h2>
          <button @click="onClose()">Close</button>
        </div>
      </div>
    </template>
  </DayFlowCalendar>
</template>

<script setup>
import { DayFlowCalendar } from '@dayflow/vue';
</script>
<dayflow-calendar
  [calendar]="calendar"
  [eventDetailDialog]="customDialog"
>
</dayflow-calendar>

<ng-template #customDialog let-args>
  <div *ngIf="args.isOpen" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
    <div class="bg-white p-6 rounded-lg shadow-xl">
      <h2>{{ args.event.title }}</h2>
      <button (click)="args.onClose()">Close</button>
    </div>
  </div>
</ng-template>
<DayFlowCalendar {calendar} eventDetailDialog={CustomDialog} />

<!-- CustomDialog.svelte -->
<script lang="ts">
  import type { EventDetailDialogSlotArgs } from '@dayflow/core';
  let { event, isOpen, onClose } = $props<EventDetailDialogSlotArgs>();
</script>

{#if isOpen}
  <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
    <div class="bg-white p-6 rounded-lg shadow-xl">
      <h2>{event.title}</h2>
      <button onclick={onClose}>Close</button>
    </div>
  </div>
{/if}

Termindetails auf Mobilgeräten

Auf Mobilgeräten – oder allgemein auf kleinen Bildschirmen – nutzt DayFlow den eigenen Slot mobileEventDetail für das Anlegen und Bearbeiten von Terminen. Standardmäßig ist das ein bildschirmfüllender Bereich.

Tap an event to open the mobile drawer. This showcase runs in an iframe so DayFlow reads a real mobile viewport instead of the desktop window.

Quellcode ansehen
import { DayFlowCalendar } from '@dayflow/react';
import { useCallback } from 'react';

export const MobileEventDetailShowcase = () => {
  const customMobileDrawer = useCallback(
    ({ isOpen, onClose, onSave, onEventDelete, draftEvent }) => {
      if (!isOpen || !draftEvent) return null;

      return (
        <div className='fixed inset-0 z-50 flex flex-col bg-white'>
          <header className='flex items-center justify-between p-4 border-b'>
            <button onClick={onClose}>Back</button>
            <h2>{draftEvent.id ? 'Edit' : 'New'} Event</h2>
            <button onClick={() => onSave(draftEvent)}>Save</button>
          </header>
          <div className='p-4'>
            <input
              defaultValue={draftEvent.title}
              onChange={e => (draftEvent.title = e.target.value)}
            />
            {/* ... build your mobile-optimized UI */}
          </div>
        </div>
      );
    },
    []
  );

  return (
    <DayFlowCalendar
      calendar={calendar}
      mobileEventDetail={customMobileDrawer}
    />
  );
};
<template>
  <DayFlowCalendar :calendar="calendar">
    <template #mobileEventDetail="{ isOpen, onClose, onSave, onEventDelete, draftEvent }">
      <div v-if="isOpen && draftEvent" class="fixed inset-0 z-50 flex flex-col bg-white">
        <header class="flex items-center justify-between p-4 border-b">
          <button @click="onClose()">Back</button>
          <h2>{{ draftEvent.id ? 'Edit' : 'New' }} Event</h2>
          <button @click="onSave(draftEvent)">Save</button>
        </header>
        <div class="p-4">
          <input
            :value="draftEvent.title"
            @input="draftEvent.title = $event.target.value"
          />
        </div>
      </div>
    </template>
  </DayFlowCalendar>
</template>

<script setup>
import { DayFlowCalendar } from '@dayflow/vue';
</script>
<dayflow-calendar
  [calendar]="calendar"
  [mobileEventDetail]="customMobileDrawer"
>
</dayflow-calendar>

<ng-template #customMobileDrawer let-args>
  <div *ngIf="args.isOpen && args.draftEvent" class="fixed inset-0 z-50 flex flex-col bg-white">
    <header class="flex items-center justify-between p-4 border-b">
      <button (click)="args.onClose()">Back</button>
      <h2>{{ args.draftEvent.id ? 'Edit' : 'New' }} Event</h2>
      <button (click)="args.onSave(args.draftEvent)">Save</button>
    </header>
    <div class="p-4">
      <input
        [value]="args.draftEvent.title"
        (input)="args.draftEvent.title = $any($event.target).value"
      />
    </div>
  </div>
</ng-template>
<DayFlowCalendar {calendar} mobileEventDetail={CustomMobileDrawer} />

<!-- CustomMobileDrawer.svelte -->
<script lang="ts">
  import type { MobileEventDetailSlotArgs } from '@dayflow/core';
  let { isOpen, onClose, onSave, draftEvent } = $props<MobileEventDetailSlotArgs>();
</script>

{#if isOpen && draftEvent}
  <div class="fixed inset-0 z-50 flex flex-col bg-white">
    <header class="flex items-center justify-between p-4 border-b">
      <button onclick={onClose}>Back</button>
      <h2>{draftEvent.id ? 'Edit' : 'New'} Event</h2>
      <button onclick={() => onSave(draftEvent)}>Save</button>
    </header>
    <div class="p-4">
      <input
        value={draftEvent.title}
        oninput={(e) => { if(draftEvent) draftEvent.title = e.currentTarget.value; }}
      />
    </div>
  </div>
{#/if}

Beispiel: einen eigenen Farbwähler einsetzen

Standardmäßig verwendet DayFlow den eingebauten BlossomColorPicker. Bevorzugen Sie eine Bibliothek wie react-color oder vue-color, setzen Sie diese über die colorPicker-Slots ein.

Color Picker

Beispiel in React

Installieren Sie react-color:

npm install react-color
pnpm add react-color
yarn add react-color
bun add react-color

Setzen Sie ihn in DayFlowCalendar ein:

Quellcode ansehen
import { DayFlowCalendar } from '@dayflow/react';
import { SketchPicker, PhotoshopPicker } from 'react-color';

function MyCalendar() {
  return (
    <DayFlowCalendar
      calendar={calendar}
      colorPicker={args => (
        <SketchPicker
          color={args.color}
          onChange={color => args.onChange({ hex: color.hex })}
        />
      )}
      createCalendarDialogColorPicker={args => (
        <PhotoshopPicker
          color={args.color}
          onChange={color => args.onChange({ hex: color.hex })}
          onAccept={args.onAccept}
          onCancel={args.onCancel}
        />
      )}
    />
  );
}
<template>
  <DayFlowCalendar :calendar="calendar">
    <template #colorPicker="args">
      <SketchPicker
        :color="args.color"
        @change="args.onChange({ hex: $event.hex })"
      />
    </template>
    <template #createCalendarDialogColorPicker="args">
      <PhotoshopPicker
        :color="args.color"
        @change="args.onChange({ hex: $event.hex })"
        @accept="args.onAccept"
        @cancel="args.onCancel"
      />
    </template>
  </DayFlowCalendar>
</template>

<script setup>
import { DayFlowCalendar } from '@dayflow/vue';
import { SketchPicker, PhotoshopPicker } from 'vue-color';
</script>
<dayflow-calendar
  [calendar]="calendar"
  [colorPicker]="picker"
  [createCalendarDialogColorPicker]="dialogPicker"
>
</dayflow-calendar>

<ng-template #picker let-args>
  <sketch-picker
    [color]="args.color"
    (change)="args.onChange({ hex: $event.hex })"
  >
  </sketch-picker>
</ng-template>

<ng-template #dialogPicker let-args>
  <photoshop-picker
    [color]="args.color"
    (change)="args.onChange({ hex: $event.hex })"
    (accept)="args.onAccept()"
    (cancel)="args.onCancel()"
  >
  </photoshop-picker>
</ng-template>
<DayFlowCalendar
  {calendar}
  colorPicker={CustomColorPicker}
  createCalendarDialogColorPicker={CustomDialogColorPicker}
/>

<!-- CustomColorPicker.svelte -->
<script lang="ts">
  import type { ColorPickerSlotArgs } from '@dayflow/core';
  let { color, onChange } = $props<ColorPickerSlotArgs>();
</script>

<!-- Render custom picker element/component here -->

<!-- CustomDialogColorPicker.svelte -->
<script lang="ts">
  import type { CreateCalendarDialogColorPickerSlotArgs } from '@dayflow/core';
  let { color, onChange, onAccept, onCancel } = $props<CreateCalendarDialogColorPickerSlotArgs>();
</script>

<!-- Render custom dialog picker element/component here -->

Kontextmenü-Slots

Mit den Slots eventContextMenu und gridContextMenu ersetzen Sie die Standard-Kontextmenüs durch vollständig eigene React-Komponenten. Beide erhalten einen onClose-Callback zum Schließen des Menüs.

  • eventContextMenu – wird beim Rechtsklick auf einen Termin ausgelöst. Erhält { event, onClose }.
  • gridContextMenu – wird beim Rechtsklick auf einen leeren Bereich des Rasters ausgelöst. Erhält { date, viewType, onClose }.

Klicken Sie mit der rechten Maustaste auf einen Termin oder eine leere Zelle, um die eigenen Menüs in Aktion zu sehen:

Quellcode ansehen
import { DayFlowCalendar } from '@dayflow/react';
import type {
  EventContextMenuSlotArgs,
  GridContextMenuSlotArgs,
} from '@dayflow/core';
import { useCallback } from 'react';

function MyCalendar() {
  const eventContextMenu = useCallback(
    ({ event, onClose }: EventContextMenuSlotArgs) => (
      <div className='custom-menu'>
        <button
          onClick={() => {
            /* duplicate */ onClose();
          }}
        >
          Duplicate
        </button>
        <button
          onClick={() => {
            /* share */ onClose();
          }}
        >
          Share
        </button>
        <button
          onClick={() => {
            calendar.deleteEvent(event.id);
            onClose();
          }}
        >
          Delete
        </button>
      </div>
    ),
    []
  );

  const gridContextMenu = useCallback(
    ({ date, onClose }: GridContextMenuSlotArgs) => (
      <div className='custom-menu'>
        <button
          onClick={() => {
            /* create event at date */ onClose();
          }}
        >
          New Event
        </button>
        <button
          onClick={() => {
            /* set reminder */ onClose();
          }}
        >
          Remind me
        </button>
      </div>
    ),
    []
  );

  return (
    <DayFlowCalendar
      calendar={calendar}
      eventContextMenu={eventContextMenu}
      gridContextMenu={gridContextMenu}
    />
  );
}
<template>
  <DayFlowCalendar :calendar="calendar">
    <template #eventContextMenu="{ event, onClose }">
      <div class="custom-menu">
        <button @click="onClose()">Duplicate</button>
        <button @click="onClose()">Share</button>
        <button @click="calendar.deleteEvent(event.id); onClose()">Delete</button>
      </div>
    </template>

    <template #gridContextMenu="{ date, onClose }">
      <div class="custom-menu">
        <button @click="onClose()">New Event</button>
        <button @click="onClose()">Remind me</button>
      </div>
    </template>
  </DayFlowCalendar>
</template>

<script setup>
import { DayFlowCalendar } from '@dayflow/vue';
</script>
<dayflow-calendar
  [calendar]="calendar"
  [eventContextMenu]="eventMenu"
  [gridContextMenu]="gridMenu"
>
</dayflow-calendar>

<ng-template #eventMenu let-args>
  <div class="custom-menu">
    <button (click)="args.onClose()">Duplicate</button>
    <button (click)="args.onClose()">Share</button>
    <button (click)="calendar.deleteEvent(args.event.id); args.onClose()">Delete</button>
  </div>
</ng-template>

<ng-template #gridMenu let-args>
  <div class="custom-menu">
    <button (click)="args.onClose()">New Event</button>
    <button (click)="args.onClose()">Remind me</button>
  </div>
</ng-template>
<DayFlowCalendar
  {calendar}
  eventContextMenu={CustomEventMenu}
  gridContextMenu={CustomGridMenu}
/>

<!-- CustomEventMenu.svelte -->
<script lang="ts">
  import type { EventContextMenuSlotArgs } from '@dayflow/core';
  let { event, onClose, calendar } = $props<EventContextMenuSlotArgs>();
</script>

<div class="custom-menu">
  <button onclick={onClose}>Duplicate</button>
  <button onclick={onClose}>Share</button>
  <button onclick={() => { calendar.deleteEvent(event.id); onClose(); }}>Delete</button>
</div>

<!-- CustomGridMenu.svelte -->
<script lang="ts">
  import type { GridContextMenuSlotArgs } from '@dayflow/core';
  let { onClose } = $props<GridContextMenuSlotArgs>();
</script>

<div class="custom-menu">
  <button onclick={onClose}>New Event</button>
  <button onclick={onClose}>Remind me</button>
</div>

Läuft die Jahresansicht im Modus grid mit gridDateClick: 'popup' (Voreinstellung), öffnet ein Klick auf eine Datumszelle ein Popup mit den Terminen dieses Tages. Über den Slot gridPopupContent ersetzen Sie den Inhalt dieses Popups durch eine eigene Oberfläche.

Der Slot muss als Prop an <DayFlowCalendar> übergeben werden – nicht an createYearView. Von einem Framework gerenderte Knoten (React, Vue usw.) müssen durch das Slot-System laufen; über die Ansichtskonfiguration des Kerns übergeben, bringen sie Preact zum Absturz.

import {
  useCalendarApp,
  DayFlowCalendar,
  createYearView,
  ViewType,
  type GridPopupContentSlotArgs,
} from '@dayflow/react';

function renderYearGridPopup({ date, events }: GridPopupContentSlotArgs) {
  return (
    <div className='rounded-xl border bg-white p-3 shadow-xl'>
      <div className='text-sm font-semibold'>{date.toDateString()}</div>
      {events.length === 0 ? (
        <div className='text-gray-400'>No events</div>
      ) : (
        events.map(e => <div key={e.id}>{e.title}</div>)
      )}
    </div>
  );
}

function MyCalendar() {
  const calendar = useCalendarApp({
    views: [
      createYearView({
        mode: 'grid',
        gridDateClick: 'popup',
      }),
    ],
    defaultView: ViewType.YEAR,
  });

  return (
    <DayFlowCalendar
      calendar={calendar}
      gridPopupContent={renderYearGridPopup}
    />
  );
}