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
- Verfügbare Slots
- Termininhalt
- Inhalt der Termindetails
- Termindetail-Dialog
- Termindetails auf Mobilgeräten
- Eigenen Farbwähler einsetzen
- Kontextmenü
- Popup im Raster der Jahresansicht
Funktionsweise
- Der Kern definiert einen Slot: In
@dayflow/coresind bestimmte UI-Bereiche in einenContentSlotgehüllt. Jeder Slot hat einengeneratorNameundgeneratorArgs. - 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-Name | Beschreibung | Argumente |
|---|---|---|
colorPicker | Ein kleiner Farbwähler für Terminfarben. | { color, onChange, onChangeComplete } |
createCalendarDialogColorPicker | Ein 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 } |
eventContextMenu | Eigenes Kontextmenü für Termine. | { event, onClose } |
eventDetailContent | Der Inhalt, der im Popover bzw. Panel erscheint, wenn Termindetails geöffnet werden. | { event, isAllDay, onEventDelete, onEventUpdate, onClose, app } |
eventDetailDialog | Eigener Dialog beim Öffnen der Termindetails. Erfordert useEventDetailDialog: true in useCalendarApp. | { event, isOpen, isAllDay, onEventDelete, onEventUpdate, onClose, app } |
gridContextMenu | Eigenes Kontextmenü für Kalenderzellen bzw. das Raster. | { date, viewType, onClose } |
gridPopupContent | Eigener Popup-Inhalt im grid-Modus der Jahresansicht (bei gridDateClick: 'popup'). | { date, events } |
mobileEventDetail | Eigener Detailbereich bzw. Dialog für Termine auf Mobilgeräten. | { isOpen, onClose, onSave, onEventDelete, draftEvent, app, timeFormat } |
sidebarCalendarColorPicker | Farbwähler für Kalenderfarben in der Seitenleiste. | { color, onChange, onChangeComplete } |
titleBarSlot | Zusä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-Name | Beschreibung |
|---|---|
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: trueinuseCalendarApp, 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-KonfigurationuseEventDetailPanel: 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.
Beispiel in React
Installieren Sie 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>Popup im Raster der Jahresansicht
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}
/>
);
}