Plugin de barra lateral
El plugin de barra lateral añade a tu calendario un panel de gestión de calendarios. Incluye un minicalendario para navegar entre fechas, una lista de calendarios con conmutadores de visibilidad y operaciones CRUD completas (crear, renombrar, cambiar de color, fusionar, eliminar, importar y exportar).
Instalación
Instala el paquete del plugin:
Uso
import { useCalendarApp, DayFlowCalendar } from '@dayflow/react';
import { createSidebarPlugin } from '@dayflow/plugin-sidebar';
function MyCalendar() {
const sidebarPlugin = createSidebarPlugin({
width: 280,
createCalendarMode: 'modal',
});
const calendar = useCalendarApp({
views: [
/* your views */
],
plugins: [sidebarPlugin],
});
return <DayFlowCalendar calendar={calendar} />;
}<template>
<DayFlowCalendar :calendar="calendar" />
</template>
<script setup>
import { DayFlowCalendar, useCalendarApp } from '@dayflow/vue';
import { createSidebarPlugin } from '@dayflow/plugin-sidebar';
const sidebarPlugin = createSidebarPlugin({
width: 280,
createCalendarMode: 'modal',
});
const calendar = useCalendarApp({
views: [
/* your views */
],
plugins: [sidebarPlugin],
});
</script>import { Component } from '@angular/core';
import { DayFlowCalendarModule } from '@dayflow/angular';
import { createSidebarPlugin } from '@dayflow/plugin-sidebar';
@Component({
selector: 'app-root',
standalone: true,
imports: [DayFlowCalendarModule],
template: `<dayflow-calendar [calendar]="calendar"></dayflow-calendar>`
})
export class AppComponent {
calendar = {
views: [
/* your views */
],
plugins: [
createSidebarPlugin({
width: 280,
createCalendarMode: 'modal',
})
]
};
}<script>
import { DayFlowCalendar, useCalendarApp } from '@dayflow/svelte';
import { createSidebarPlugin } from '@dayflow/plugin-sidebar';
const sidebarPlugin = createSidebarPlugin({
width: 280,
createCalendarMode: 'modal',
});
const calendar = useCalendarApp({
views: [
/* your views */
],
plugins: [sidebarPlugin],
});
</script>
<DayFlowCalendar {calendar} />Configuración
| Propiedad | Tipo | Valor por defecto | Descripción |
|---|---|---|---|
width | number | string | '240px' | Anchura de la barra lateral (por ejemplo, 280 o '20rem') |
miniWidth | string | '50px' | Anchura de la barra lateral cuando está plegada |
initialCollapsed | boolean | false | Indica si la barra lateral arranca plegada |
createCalendarMode | 'inline' | 'modal' | 'inline' | Cómo se muestra el formulario de creación de calendarios |
colorPickerMode | 'blossom' | 'default' | 'default' | Qué componente selector de color se usa |
onSubscribeCalendar | (calendar, events) => Promise<void> | undefined | Callback que se ejecuta tras crear una suscripción |
onLoadSubscription | (calendar) => Promise<void> | undefined | Cargador propio para las suscripciones existentes |
onReorder | (calendars: CalendarType[]) => void | Promise<void> | undefined | Callback que se ejecuta tras reordenar los calendarios |
componentsOrder | ('calendarList' | 'miniCalendar')[] | ['calendarList', 'miniCalendar'] | Orden en que se muestran los componentes de la barra lateral |
render | (props: CalendarSidebarRenderProps) => TNode | undefined | Sustitución completa de la interfaz de la barra lateral |
renderSidebarHeader | (args: SidebarHeaderSlotArgs) => TNode | undefined | Renderizador propio para la cabecera de la barra lateral |
renderCalendarContextMenu | (calendar, onClose) => TNode | undefined | Renderizador propio del menú contextual de cada calendario |
renderCreateCalendarDialog | (props) => TNode | undefined | Renderizador propio del diálogo de creación de calendarios |
API programática
Una vez renderizado el calendario, accede a SidebarService mediante app.getPlugin:
import { type SidebarService } from '@dayflow/plugin-sidebar';
const sidebar = app.getPlugin<SidebarService>('sidebar');
sidebar?.collapse(); // Collapse the sidebar
sidebar?.expand(); // Expand the sidebar
sidebar?.setCollapsed(true); // Set state explicitly
console.log(sidebar?.isCollapsed()); // Read current stateSidebarService
| Método | Devuelve | Descripción |
|---|---|---|
collapse() | void | Pliega la barra lateral |
expand() | void | Despliega la barra lateral |
setCollapsed(collapsed: boolean) | void | Fija el estado plegado de forma explícita |
isCollapsed() | boolean | Devuelve el estado plegado actual |
Asociarla a un atajo de teclado
window.addEventListener('keydown', e => {
if (e.metaKey && e.key === '[') {
const sidebar = app.getPlugin<SidebarService>('sidebar');
sidebar?.setCollapsed(!sidebar.isCollapsed());
}
});Funcionalidades
La barra lateral ofrece un diseño listo para usar con conmutadores de visibilidad, muestras de color de cada calendario y controles de plegado. También permite crear calendarios y usar menús contextuales, de modo que los usuarios pueden añadir calendarios, cambiarles el color y eliminarlos.
Funcionalidades integradas:
- Minicalendario: una vista mensual compacta para navegar rápidamente entre fechas
- Lista de calendarios: muestra u oculta cada calendario mediante casillas con código de color
- Crear calendario: añade calendarios nuevos con nombre y color propios
- Renombrar y cambiar color: haz clic derecho en un calendario para renombrarlo o cambiar su color
- Reordenar calendarios: arrastra y suelta los calendarios de la lista para cambiar su orden. Usa el callback
onReorderpara guardar el nuevo orden en tu backend. - Fusionar calendarios: mueve todos los eventos de un calendario a otro
- Eliminar calendario: borra un calendario con un paso de confirmación (o fusiónalo antes)
- Importar y exportar: importa archivos
.icso exporta calendarios a.ics - Suscribirse a un calendario: suscríbete por URL a feeds
.icsremotos; los calendarios suscritos muestran un distintivo en la lista
Demostración en vivo
Explora abajo la interfaz integrada de la barra lateral y una implementación propia con componentes nativos del framework.
Qué probar
- Conmutadores de visibilidad: oculta y muestra calendarios para ver cómo se actualizan las vistas al instante.
- Arrastrar y sincronizar: arrastra eventos entre vistas; la barra lateral mantiene sincronizados los próximos destacados.
- Plegado: pliega la barra lateral para ganar espacio cuando trabajes con vistas semanales densas.
Suscribirse a un calendario
La barra lateral incluye una función de suscripción que permite añadir feeds ICS remotos. Para usarla, haz clic derecho sobre cualquier calendario o sobre el área vacía de la barra lateral para abrir el menú contextual, elige Suscribirse a un calendario e introduce cualquier URL .ics de acceso público.
DayFlow descarga el archivo, analiza los eventos y crea un calendario nuevo automáticamente. Los calendarios suscritos muestran un pequeño distintivo en la lista para diferenciarlos de los creados localmente.
Carga automática y deduplicación
Cuando defines un calendario con subscription.url en la configuración inicial, el plugin de barra lateral descarga automáticamente los últimos eventos al montarse.
Para evitar duplicados visuales al combinar los eventos guardados en caché con los datos recién descargados, DayFlow aplica una deduplicación basada en el ID:
- Si un evento de una suscripción tiene el mismo
idque otro ya presente en el almacén del núcleo, prevalece la versión de la suscripción. - Así, la interfaz siempre muestra la información más reciente sin que los eventos se «dupliquen» al recargar la página.
Persistir los calendarios suscritos
DayFlow reconoce los calendarios suscritos por la presencia de un objeto subscription en el CalendarType.
Comportamiento predeterminado de las suscripciones:
- Solo lectura: las modificaciones desde la interfaz (editar el título, cambiar de calendario, eliminar) se desactivan mediante
canMutateFromUI(). - Sin arrastre: las acciones de arrastrar y soltar (mover, redimensionar) están desactivadas.
- Notas ocultas: si un evento no tiene descripción, el campo «Nota» se oculta en el panel de detalle para no recargar la interfaz.
Si quieres que los usuarios puedan modificar o mover los eventos suscritos, desactiva estas protecciones explícitamente con readOnly: false en el objeto del calendario:
const calendar = useCalendarApp({
calendars: [
{
id: 'team-ics',
name: 'Team Calendar',
subscription: {
url: 'https://example.com/calendar.ics',
status: 'ready',
},
// Override default protections:
readOnly: false, // Enables both UI mutations and drag-and-drop
colors: {
/* ... */
},
},
],
});Al guardar el estado de los calendarios en tu backend, conserva el campo subscription y restáuralo en la siguiente carga.
Nota: DayFlow no actualiza los feeds suscritos automáticamente. Para mantener los eventos al día, implementa una descarga periódica en tu aplicación y actualízalos con
app.addEvent()/app.removeEvent().
Puedes sustituir la barra lateral por tu propio componente pasando una función render a createSidebarPlugin (React) o usando los slots o plantillas de componentes (Vue, Angular, Svelte).
import { useCalendarApp, DayFlowCalendar } from '@dayflow/react';
import {
createSidebarPlugin,
type CalendarSidebarRenderProps,
} from '@dayflow/plugin-sidebar';
const CustomSidebar = ({
app,
calendars,
toggleCalendarVisibility,
isCollapsed,
setCollapsed,
}: CalendarSidebarRenderProps) => {
if (isCollapsed) {
return (
<div className='p-2'>
<button onClick={() => setCollapsed(false)}>→</button>
</div>
);
}
return (
<aside className='flex h-full flex-col gap-4 p-4 bg-slate-50 border-r'>
<header className='flex items-center justify-between'>
<h3 className='font-semibold'>My Workspace</h3>
<button onClick={() => setCollapsed(true)}>←</button>
</header>
<nav className='space-y-1'>
{calendars.map(calendar => (
<label
key={calendar.id}
className='flex items-center gap-2 cursor-pointer'
>
<input
type='checkbox'
checked={calendar.isVisible}
onChange={e =>
toggleCalendarVisibility(calendar.id, e.target.checked)
}
/>
<span
className='w-3 h-3 rounded-full'
style={{ backgroundColor: calendar.colors.lineColor }}
/>
{calendar.name}
</label>
))}
</nav>
<div className='mt-auto pt-4 border-t text-xs text-slate-500'>
Total Events: {app.getEvents().length}
</div>
</aside>
);
};
function App() {
const calendar = useCalendarApp({
views: [/* views */],
plugins: [
createSidebarPlugin({
render: props => <CustomSidebar {...props} />,
}),
],
});
return <DayFlowCalendar calendar={calendar} />;
}<template>
<DayFlowCalendar :calendar="calendar">
<!-- Override using scoped slot 'sidebar' -->
<template #sidebar="args">
<aside v-if="!args.isCollapsed" class="flex h-full flex-col gap-4 p-4 bg-slate-50 border-r">
<header class="flex items-center justify-between">
<h3 class="font-semibold">My Workspace</h3>
<button @click="args.setCollapsed(true)">←</button>
</header>
<nav class="space-y-1">
<label
v-for="cal in args.calendars"
:key="cal.id"
class="flex items-center gap-2 cursor-pointer"
>
<input
type="checkbox"
:checked="cal.isVisible"
@change="args.toggleCalendarVisibility(cal.id, !cal.isVisible)"
/>
<span
class="w-3 h-3 rounded-full"
:style="{ backgroundColor: cal.colors.lineColor }"
/>
{{ cal.name }}
</label>
</nav>
<div class="mt-auto pt-4 border-t text-xs text-slate-500">
Total Events: {{ args.app.getEvents().length }}
</div>
</aside>
<div v-else class="p-2">
<button @click="args.setCollapsed(false)">→</button>
</div>
</template>
</DayFlowCalendar>
</template>
<script setup>
import { DayFlowCalendar, useCalendarApp } from '@dayflow/vue';
import { createSidebarPlugin } from '@dayflow/plugin-sidebar';
const calendar = useCalendarApp({
views: [/* views */],
plugins: [createSidebarPlugin()]
});
</script><!-- app.component.html -->
<dayflow-calendar
[calendar]="calendar"
[sidebar]="customSidebar"
>
</dayflow-calendar>
<ng-template #customSidebar let-args>
<aside *ngIf="!args.isCollapsed; else collapsedView" class="flex h-full flex-col gap-4 p-4 bg-slate-50 border-r">
<header class="flex items-center justify-between">
<h3 class="font-semibold">My Workspace</h3>
<button (click)="args.setCollapsed(true)">←</button>
</header>
<nav class="space-y-1">
<label
*ngFor="let cal of args.calendars"
class="flex items-center gap-2 cursor-pointer"
>
<input
type="checkbox"
[checked]="cal.isVisible"
(change)="args.toggleCalendarVisibility(cal.id, !cal.isVisible)"
/>
<span
class="w-3 h-3 rounded-full"
[style.background-color]="cal.colors.lineColor"
></span>
{{ cal.name }}
</label>
</nav>
<div class="mt-auto pt-4 border-t text-xs text-slate-500">
Total Events: {{ args.app.getEvents().length }}
</div>
</aside>
<ng-template #collapsedView>
<div class="p-2">
<button (click)="args.setCollapsed(false)">→</button>
</div>
</ng-template>
</ng-template><!-- App.svelte -->
<script lang="ts">
import { DayFlowCalendar, useCalendarApp } from '@dayflow/svelte';
import { createSidebarPlugin } from '@dayflow/plugin-sidebar';
import CustomSidebar from './CustomSidebar.svelte';
const calendar = useCalendarApp({
views: [/* views */],
plugins: [createSidebarPlugin()]
});
</script>
<DayFlowCalendar {calendar} sidebar={CustomSidebar} />
<!-- CustomSidebar.svelte -->
<script lang="ts">
import type { CalendarSidebarRenderProps } from '@dayflow/core';
let {
app,
calendars,
isCollapsed,
setCollapsed,
toggleCalendarVisibility,
} = $props<CalendarSidebarRenderProps>();
</script>
{#if isCollapsed}
<div class="p-2">
<button onclick={() => setCollapsed(false)}>→</button>
</div>
{:else}
<aside class="flex h-full flex-col gap-4 p-4 bg-slate-50 border-r">
<header class="flex items-center justify-between">
<h3 class="font-semibold">My Workspace</h3>
<button onclick={() => setCollapsed(true)}>←</button>
</header>
<nav class="space-y-1">
{#each calendars as cal (cal.id)}
<label class="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={cal.isVisible}
onchange={(e) => toggleCalendarVisibility(cal.id, e.currentTarget.checked)}
/>
<span
class="w-3 h-3 rounded-full"
style:background-color={cal.colors.lineColor}
></span>
{cal.name}
</label>
{/each}
</nav>
<div class="mt-auto pt-4 border-t text-xs text-slate-500">
Total Events: {app.getEvents().length}
</div>
</aside>
{/if}Menú contextual propio
Puedes sustituir el menú contextual predeterminado de los calendarios:
createSidebarPlugin({
renderCalendarContextMenu: (calendar, onClose) => (
<div className='bg-white shadow-lg border rounded p-2'>
<button
onClick={() => {
console.log('Custom action');
onClose();
}}
>
Custom Action for {calendar.name}
</button>
</div>
),
});<template>
<DayFlowCalendar :calendar="calendar">
<template #calendarContextMenu="{ calendar, onClose }">
<div class="bg-white shadow-lg border rounded p-2">
<button @click="onClose()">
Custom Action for {{ calendar.name }}
</button>
</div>
</template>
</DayFlowCalendar>
</template><dayflow-calendar
[calendar]="calendar"
[calendarContextMenu]="contextMenu"
>
</dayflow-calendar>
<ng-template #contextMenu let-args>
<div class="bg-white shadow-lg border rounded p-2">
<button (click)="args.onClose()">
Custom Action for {{ args.calendar.name }}
</button>
</div>
</ng-template><DayFlowCalendar {calendar} calendarContextMenu={CustomContextMenu} />
<!-- CustomContextMenu.svelte -->
<script lang="ts">
import type { CalendarContextMenuSlotArgs } from '@dayflow/core';
let { calendar, onClose } = $props<CalendarContextMenuSlotArgs>();
</script>
<div class="bg-white shadow-lg border rounded p-2">
<button onclick={() => { console.log('Custom action'); onClose(); }}>
Custom Action for {calendar.name}
</button>
</div>Diálogo propio de creación de calendarios
Puedes sustituir el diálogo predeterminado de creación de calendarios:
createSidebarPlugin({
renderCreateCalendarDialog: ({ onCreate, onClose }) => (
<MyCustomDialog onSave={onCreate} onCancel={onClose} />
),
});<template>
<DayFlowCalendar :calendar="calendar">
<template #createCalendarDialog="{ onCreate, onClose }">
<MyCustomDialog @save="onCreate" @cancel="onClose" />
</template>
</DayFlowCalendar>
</template><dayflow-calendar
[calendar]="calendar"
[createCalendarDialog]="customDialog"
>
</dayflow-calendar>
<ng-template #customDialog let-args>
<my-custom-dialog
(save)="args.onCreate($event)"
(cancel)="args.onClose()"
>
</my-custom-dialog>
</ng-template><DayFlowCalendar {calendar} createCalendarDialog={CustomDialog} />
<!-- CustomDialog.svelte -->
<script lang="ts">
import type { CreateCalendarDialogProps } from '@dayflow/core';
let { onCreate, onClose } = $props<CreateCalendarDialogProps>();
</script>
<div class="dialog">
<!-- custom dialog markup -->
<button onclick={() => onCreate({ id: 'new', name: 'New' })}>Create</button>
<button onclick={onClose}>Cancel</button>
</div>Cabecera propia de la barra lateral
Puedes sustituir la cabecera predeterminada de la barra lateral (la zona con el título «Calendarios» y el botón de plegado):
createSidebarPlugin({
renderSidebarHeader: ({ isCollapsed, onCollapseToggle }) => (
<div className='flex items-center justify-between p-4 border-b'>
{!isCollapsed && <span className='font-bold text-lg'>My Calendars</span>}
<button
onClick={onCollapseToggle}
className='p-1 hover:bg-slate-100 rounded'
>
{isCollapsed ? '→' : '←'}
</button>
</div>
),
});<template>
<DayFlowCalendar :calendar="calendar">
<template #sidebarHeader="{ isCollapsed, onCollapseToggle }">
<div class="flex items-center justify-between p-4 border-b">
<span v-if="!isCollapsed" class="font-bold text-lg">My Calendars</span>
<button
@click="onCollapseToggle"
class="p-1 hover:bg-slate-100 rounded"
>
{{ isCollapsed ? '→' : '←' }}
</button>
</div>
</template>
</DayFlowCalendar>
</template><dayflow-calendar
[calendar]="calendar"
[sidebarHeader]="headerTemplate"
>
</dayflow-calendar>
<ng-template #headerTemplate let-args>
<div class="flex items-center justify-between p-4 border-b">
<span *ngIf="!args.isCollapsed" class="font-bold text-lg">My Calendars</span>
<button
(click)="args.onCollapseToggle()"
class="p-1 hover:bg-slate-100 rounded"
>
{{ args.isCollapsed ? '→' : '←' }}
</button>
</div>
</ng-template><DayFlowCalendar {calendar} sidebarHeader={CustomHeader} />
<!-- CustomHeader.svelte -->
<script lang="ts">
import type { SidebarHeaderSlotArgs } from '@dayflow/core';
let { isCollapsed, onCollapseToggle } = $props<SidebarHeaderSlotArgs>();
</script>
<div class="custom-header">
{#if !isCollapsed}
<span>My Calendars</span>
{/if}
<button onclick={onCollapseToggle}>
{isCollapsed ? '→' : '←'}
</button>
</div>