Content Slots

DayFlow๋Š” ์Šฌ๋กฏ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋•๋ถ„์— ์›ํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ(React, Vue ๋“ฑ)์˜ UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ์บ˜๋ฆฐ๋” ์ฝ”์–ด ์—”์ง„์— ์ง์ ‘ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด ContentSlot ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. ์ฝ”์–ด ์—”์ง„์€ ๋Œ€๋ถ€๋ถ„์˜ UI ์š”์†Œ์— ๋Œ€ํ•ด Preact๋กœ ๋งŒ๋“  ๊ธฐ๋ณธ ๊ตฌํ˜„์„ ์ œ๊ณตํ•˜์ง€๋งŒ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋””์ž์ธ์— ๋งž์ถ”๊ฑฐ๋‚˜ ํŠน์ • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์“ฐ๊ธฐ ์œ„ํ•ด ์ด๋ฅผ ๊ต์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ชฉ์ฐจ

๋™์ž‘ ๋ฐฉ์‹

  1. ์ฝ”์–ด๊ฐ€ ์Šฌ๋กฏ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค: @dayflow/core ์•ˆ์—์„œ ํŠน์ • UI ์˜์—ญ์ด ContentSlot์œผ๋กœ ๊ฐ์‹ธ์—ฌ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ์Šฌ๋กฏ์—๋Š” generatorName๊ณผ generatorArgs๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. ๊ตฌํ˜„์€ ์—ฌ๋Ÿฌ๋ถ„์ด ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค: DayFlowCalendar ์ปดํฌ๋„ŒํŠธ์— ์ปดํฌ๋„ŒํŠธ๋‚˜ ๋ Œ๋” ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜๋ฉด, ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์ฝ”์–ด๊ฐ€ ์ง€์ •ํ•œ ์ •ํ™•ํ•œ ์œ„์น˜๋กœ ํฌํ„ธ์„ ํ†ตํ•ด ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์Šฌ๋กฏ

์ œ๋„ˆ๋ ˆ์ดํ„ฐ ์ด๋ฆ„์„ค๋ช…์ธ์ž
colorPicker์ด๋ฒคํŠธ ์ƒ‰์ƒ์„ ๊ณ ๋ฅด๋Š” ์ž‘์€ ์ƒ‰์ƒ ์„ ํƒ๊ธฐ์ž…๋‹ˆ๋‹ค.{ color, onChange, onChangeComplete }
createCalendarDialogColorPicker๋Œ€ํ™”์ƒ์ž์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋Šฅ์ด ํ’๋ถ€ํ•œ ์ƒ‰์ƒ ์„ ํƒ๊ธฐ์ž…๋‹ˆ๋‹ค.{ color, onChange, onAccept, onCancel, styles }
eventContent*์ด๋ฒคํŠธ ์นด๋“œ์˜ ์‚ฌ์šฉ์ž ์ •์˜ ๋ Œ๋”๋ง์ž…๋‹ˆ๋‹ค(์˜ˆ: eventContentDay).{ event, viewType, isAllDay, isMobile, isSelected, isDragging, layout }
eventContextMenu์ด๋ฒคํŠธ์šฉ ์‚ฌ์šฉ์ž ์ •์˜ ์šฐํด๋ฆญ ์ปจํ…์ŠคํŠธ ๋ฉ”๋‰ด์ž…๋‹ˆ๋‹ค.{ event, onClose }
eventDetailContent์ด๋ฒคํŠธ ์ƒ์„ธ๋ฅผ ์—ด์—ˆ์„ ๋•Œ ํŒ์˜ค๋ฒ„ยทํŒจ๋„์— ํ‘œ์‹œ๋˜๋Š” ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.{ event, isAllDay, onEventDelete, onEventUpdate, onClose, app }
eventDetailDialog์ด๋ฒคํŠธ ์ƒ์„ธ๋ฅผ ์—ด ๋•Œ ํ‘œ์‹œํ•  ์‚ฌ์šฉ์ž ์ •์˜ ๋Œ€ํ™”์ƒ์ž์ž…๋‹ˆ๋‹ค. useCalendarApp์— useEventDetailDialog: true๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.{ event, isOpen, isAllDay, onEventDelete, onEventUpdate, onClose, app }
gridContextMenu์บ˜๋ฆฐ๋” ์…€ยท๊ทธ๋ฆฌ๋“œ์šฉ ์‚ฌ์šฉ์ž ์ •์˜ ์šฐํด๋ฆญ ์ปจํ…์ŠคํŠธ ๋ฉ”๋‰ด์ž…๋‹ˆ๋‹ค.{ date, viewType, onClose }
gridPopupContent์—ฐ๊ฐ„ ๋ทฐ grid ๋ชจ๋“œ์˜ ์‚ฌ์šฉ์ž ์ •์˜ ํŒ์—… ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค(gridDateClick: 'popup'์ผ ๋•Œ).{ date, events }
mobileEventDetail๋ชจ๋ฐ”์ผ์šฉ ์‚ฌ์šฉ์ž ์ •์˜ ์ด๋ฒคํŠธ ์ƒ์„ธ ๋“œ๋กœ์–ดยท๋Œ€ํ™”์ƒ์ž์ž…๋‹ˆ๋‹ค.{ isOpen, onClose, onSave, onEventDelete, draftEvent, app, timeFormat }
sidebarCalendarColorPicker์‚ฌ์ด๋“œ๋ฐ”์—์„œ ์บ˜๋ฆฐ๋” ์ƒ‰์ƒ์„ ๊ณ ๋ฅด๋Š” ์ƒ‰์ƒ ์„ ํƒ๊ธฐ์ž…๋‹ˆ๋‹ค.{ color, onChange, onChangeComplete }
titleBarSlot์‚ฌ์ด๋“œ๋ฐ” ์ œ๋ชฉ ํ‘œ์‹œ์ค„์— ์ถ”๊ฐ€ํ•  ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.{ isCollapsed, toggleCollapsed }

์ด๋ฒคํŠธ ์ฝ˜ํ…์ธ 

eventContent ์Šฌ๋กฏ์„ ์‚ฌ์šฉํ•˜๋ฉด ์บ˜๋ฆฐ๋”์—์„œ ์ด๋ฒคํŠธ๊ฐ€ ๋ Œ๋”๋ง๋˜๋Š” ๋ฐฉ์‹์„ ์™„์ „ํžˆ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์Šฌ๋กฏ๊ณผ ๋‹ฌ๋ฆฌ ์ด๋ฒคํŠธ ์ฝ˜ํ…์ธ ๋Š” ๋ทฐ๋งˆ๋‹ค ๋ฐ˜๋“œ์‹œ ๋”ฐ๋กœ ์ง€์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ๋ทฐ์˜ ์ด๋ฒคํŠธ ๊ตฌ์กฐ์— ๋งž๋Š” ๋ ˆ์ด์•„์›ƒ์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ์ž…๋‹ˆ๋‹ค.

๋ทฐ๋ณ„ ์Šฌ๋กฏ

์Šฌ๋กฏ ์ด๋ฆ„์„ค๋ช…
eventContentDay์ผ ๋ทฐ์˜ ์‹œ๊ฐ„ ์ง€์ • ์ด๋ฒคํŠธ๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.
eventContentWeek์ฃผ ๋ทฐ์˜ ์‹œ๊ฐ„ ์ง€์ • ์ด๋ฒคํŠธ๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.
eventContentMonth์›” ๋ทฐ์˜ ์ด๋ฒคํŠธ๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.
eventContentYear์—ฐ๊ฐ„ ๋ทฐ์˜ ์ด๋ฒคํŠธ๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.
eventContentAllDayDay์ผ ๋ทฐ์˜ ์ข…์ผ ์ด๋ฒคํŠธ๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.
eventContentAllDayWeek์ฃผ ๋ทฐ์˜ ์ข…์ผ ์ด๋ฒคํŠธ๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.
eventContentAllDayMonth์›” ๋ทฐ์˜ ์ข…์ผ ์ด๋ฒคํŠธ๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.
eventContentAllDayYear์—ฐ๊ฐ„ ๋ทฐ์˜ ์ข…์ผ ์ด๋ฒคํŠธ๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.
์†Œ์Šค ์ฝ”๋“œ ๋ณด๊ธฐ
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>

์ด๋ฒคํŠธ ์ƒ์„ธ ์ฝ˜ํ…์ธ 

eventDetailContent ์Šฌ๋กฏ์œผ๋กœ ์ด๋ฒคํŠธ ์ƒ์„ธ๋ฅผ ์—ด์—ˆ์„ ๋•Œ ํŒ์˜ค๋ฒ„๋‚˜ ํŒจ๋„์— ํ‘œ์‹œ๋˜๋Š” ๋‚ด์šฉ์„ ์›ํ•˜๋Š” ๋Œ€๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์†Œ์Šค ์ฝ”๋“œ ๋ณด๊ธฐ
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>

์ด๋ฒคํŠธ ์ƒ์„ธ ๋Œ€ํ™”์ƒ์ž

์ด๋ฒคํŠธ ์ƒ์„ธ๋ฅผ ๋ชจ๋‹ฌ ํ˜•ํƒœ๋กœ ๋ณด๊ณ  ํŽธ์ง‘ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด eventDetailDialog ์Šฌ๋กฏ์„ ์‚ฌ์šฉํ•˜์„ธ์š”. ๋ชจ๋ฐ”์ผ ์šฐ์„  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด๋‚˜, ๋ณต์žกํ•œ ์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์œ„ํ•ด ๋” ๋„“์€ ๊ณต๊ฐ„์ด ํ•„์š”ํ•  ๋•Œ ํŠนํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์ „ ์กฐ๊ฑด: ๋Œ€ํ™”์ƒ์ž ๋ชจ๋“œ๋ฅผ ์ผœ๋ ค๋ฉด useCalendarApp์— useEventDetailDialog: true๋ฅผ ์ง€์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์ด ์Šฌ๋กฏ์€ ์ „ํ˜€ ํ˜ธ์ถœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋Œ€์‹  ๋„๊ธฐ: ์ง์ ‘ ๋งŒ๋“  ๋Œ€ํ™”์ƒ์ž๋กœ ๊ต์ฒดํ•˜์ง€ ์•Š๊ณ  ๋‚ด์žฅ ํ”Œ๋กœํŒ… ํŒจ๋„๋งŒ ์—†์• ๊ณ  ์‹ถ๋‹ค๋ฉด, ์Šฌ๋กฏ ๋Œ€์‹  useCalendarApp ์„ค์ •์— useEventDetailPanel: false๋ฅผ ์ง€์ •ํ•˜์„ธ์š”.

์†Œ์Šค ์ฝ”๋“œ ๋ณด๊ธฐ
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}

๋ชจ๋ฐ”์ผ ์ด๋ฒคํŠธ ์ƒ์„ธ

๋ชจ๋ฐ”์ผ ๊ธฐ๊ธฐ(๋˜๋Š” ์ž‘์€ ํ™”๋ฉด)์—์„œ DayFlow๋Š” ์ด๋ฒคํŠธ ์ƒ์„ฑ๊ณผ ํŽธ์ง‘์„ ์œ„ํ•ด ์ „์šฉ mobileEventDetail ์Šฌ๋กฏ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ ์ „์ฒด ํ™”๋ฉด ๋“œ๋กœ์–ด์ž…๋‹ˆ๋‹ค.

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.

์†Œ์Šค ์ฝ”๋“œ ๋ณด๊ธฐ
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}

์˜ˆ์‹œ: ์‚ฌ์šฉ์ž ์ •์˜ ์ƒ‰์ƒ ์„ ํƒ๊ธฐ ์ฃผ์ž…ํ•˜๊ธฐ

DayFlow๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‚ด์žฅ BlossomColorPicker๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. react-color๋‚˜ vue-color ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์“ฐ๊ณ  ์‹ถ๋‹ค๋ฉด colorPicker ์Šฌ๋กฏ์œผ๋กœ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Color Picker

React ์˜ˆ์‹œ

react-color๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค:

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

DayFlowCalendar์— ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค:

์†Œ์Šค ์ฝ”๋“œ ๋ณด๊ธฐ
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 -->

์ปจํ…์ŠคํŠธ ๋ฉ”๋‰ด ์Šฌ๋กฏ

eventContextMenu์™€ gridContextMenu ์Šฌ๋กฏ์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ธฐ๋ณธ ์šฐํด๋ฆญ ๋ฉ”๋‰ด๋ฅผ ์ง์ ‘ ๋งŒ๋“  React ์ปดํฌ๋„ŒํŠธ๋กœ ์™„์ „ํžˆ ๊ต์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‘ ์Šฌ๋กฏ ๋ชจ๋‘ ๋ฉ”๋‰ด๋ฅผ ๋‹ซ๋Š” onClose ์ฝœ๋ฐฑ์„ ์ „๋‹ฌ๋ฐ›์Šต๋‹ˆ๋‹ค.

  • eventContextMenu โ€” ์ด๋ฒคํŠธ๋ฅผ ๋งˆ์šฐ์Šค ์˜ค๋ฅธ์ชฝ ๋ฒ„ํŠผ์œผ๋กœ ํด๋ฆญํ•  ๋•Œ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. { event, onClose }๋ฅผ ์ „๋‹ฌ๋ฐ›์Šต๋‹ˆ๋‹ค.
  • gridContextMenu โ€” ์บ˜๋ฆฐ๋” ๊ทธ๋ฆฌ๋“œ์˜ ๋นˆ ์˜์—ญ์„ ๋งˆ์šฐ์Šค ์˜ค๋ฅธ์ชฝ ๋ฒ„ํŠผ์œผ๋กœ ํด๋ฆญํ•  ๋•Œ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. { date, viewType, onClose }๋ฅผ ์ „๋‹ฌ๋ฐ›์Šต๋‹ˆ๋‹ค.

์•„๋ฌด ์ด๋ฒคํŠธ๋‚˜ ๋นˆ ์…€์„ ๋งˆ์šฐ์Šค ์˜ค๋ฅธ์ชฝ ๋ฒ„ํŠผ์œผ๋กœ ํด๋ฆญํ•ด ์‚ฌ์šฉ์ž ์ •์˜ ๋ฉ”๋‰ด๋ฅผ ํ™•์ธํ•ด ๋ณด์„ธ์š”:

์†Œ์Šค ์ฝ”๋“œ ๋ณด๊ธฐ
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>

์—ฐ๊ฐ„ ๋ทฐ ๊ทธ๋ฆฌ๋“œ ํŒ์—…

์—ฐ๊ฐ„ ๋ทฐ๊ฐ€ grid ๋ชจ๋“œ์ด๊ณ  gridDateClick: 'popup'(๊ธฐ๋ณธ๊ฐ’)์ผ ๋•Œ, ๋‚ ์งœ ์…€์„ ํด๋ฆญํ•˜๋ฉด ๊ทธ๋‚ ์˜ ์ด๋ฒคํŠธ๋ฅผ ๋‚˜์—ดํ•˜๋Š” ํŒ์—…์ด ์—ด๋ฆฝ๋‹ˆ๋‹ค. gridPopupContent ์Šฌ๋กฏ์œผ๋กœ ์ด ํŒ์—…์˜ ๋ณธ๋ฌธ์„ ์ง์ ‘ ๋งŒ๋“  UI๋กœ ๊ต์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์Šฌ๋กฏ์€ createYearView๊ฐ€ ์•„๋‹ˆ๋ผ <DayFlowCalendar>์˜ prop์œผ๋กœ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ๋ Œ๋”๋งํ•œ ๋…ธ๋“œ(React, Vue ๋“ฑ)๋Š” ๋ฐ˜๋“œ์‹œ ์Šฌ๋กฏ ์‹œ์Šคํ…œ์„ ๊ฑฐ์ณ์•ผ ํ•˜๋ฉฐ, ์ฝ”์–ด ๋ทฐ ์„ค์ •์œผ๋กœ ์ „๋‹ฌํ•˜๋ฉด Preact๊ฐ€ ํฌ๋ž˜์‹œํ•ฉ๋‹ˆ๋‹ค.

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}
    />
  );
}