---
title: "Vue Timeline Meal Planner Example | Mobiscroll"
component: "Timeline"
framework: "Vue"
library_version: "6.2.0"
url: "https://demo.mobiscroll.com/vue/timeline/meal-planner"
---

# Meal Planner - Vue Timeline demo

Build a weekly meal planner with the Vue Timeline — meal types as color-coded resource rows, custom add/edit popover for creating or updating meals. For Vue and Nuxt.

- [Live demo](https://demo.mobiscroll.com/vue/timeline/meal-planner)
- [View SFC JS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue/tree/main/src/demos/eventcalendar/timeline/meal-planner)
- [View SFC TS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue-ts/tree/main/src/demos/eventcalendar/timeline/meal-planner)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/timeline/meal-planner.md), [jQuery](https://demo.mobiscroll.com/jquery/timeline/meal-planner.md), [Angular](https://demo.mobiscroll.com/angular/timeline/meal-planner.md), [React](https://demo.mobiscroll.com/react/timeline/meal-planner.md)

## Description

Use the [timeline summary mode/event listing](https://demo.mobiscroll.com/vue/timeline/event-listing) to create a weekly meal planner. Meal types are represented as resources with a [custom template](https://demo.mobiscroll.com/vue/scheduler/custom-resource-header-template). Events are the actual meals with custom properties, like calories or notes.

The add/edit form shows up in a custom popover that opens on double click for meal creation and on click for editing.

The popover header is customized with a custom header template using the [`header`](https://mobiscroll.com/docs/vue/eventcalendar/api#slot-header) template slot.

By default the second dimension of the timeline (vertical axis) is reserved for resources, however it can be configured and used as "time slots" if the times are not relevant.

## What this demo shows

- Displays a weekly timeline-based meal planner where days are shown horizontally and meal types are shown as resource rows.
- **Resources** Each resource row represents a meal type, and each meal type has its own color so related events are easy to distinguish.
- **Events** Events represent the planned food item for a specific day and meal, using the resource color across the timeline cell.
- **Editing** Clicking an existing meal opens a popup with an edit form for updating the meal name, calories, and notes.
- **Deleting** The edit popup includes a delete action that removes the selected meal immediately, then shows a snackbar confirmation with an Undo action to restore it.
- **Creating** Double-clicking an empty timeline cell opens a popup for creating a new meal for the selected day and meal row.
- **Form behavior** The create popup lets users enter the meal name, calories, and notes before saving the new event.

## Best for

- **Meal planning** Weekly meal planning workflows for restaurants, schools, kindergartens, nursing homes, and similar institutions.
- **Day-based planning** Scenarios where items need to be planned by day without showing exact start and end times.
- **Resource-based organization** Setups where color-coded meal categories or resource rows make planned items easier to scan.
- **Direct calendar editing** Applications that need users to add, edit, delete, and restore planned items directly from the calendar UI.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-themeVariant) - option
- [`display`](https://mobiscroll.com/docs/vue/popup/api#opt-display) - option
- [`width`](https://mobiscroll.com/docs/vue/popup/api#opt-width) - option
- [`fullScreen`](https://mobiscroll.com/docs/vue/popup/api#opt-fullScreen) - option
- [`touchUi`](https://mobiscroll.com/docs/vue/popup/api#opt-touchUi) - option
- [`showOverlay`](https://mobiscroll.com/docs/vue/popup/api#opt-showOverlay) - option
- [`cssClass`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-cssClass) - option
- [`view`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-view) - option
- [`data`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-data) - option
- [`resources`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-resources) - option
- [`dragToCreate`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToCreate) - option
- [`dragToResize`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToResize) - option
- [`dragToMove`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToMove) - option
- [`clickToCreate`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-clickToCreate) - option
- [`extendDefaultEvent`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-extendDefaultEvent) - option
- [`event-click`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventClick) - event
- [`event-created`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventCreated) - event
- [`event-deleted`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventDeleted) - event
- [`cssClass`](https://mobiscroll.com/docs/vue/popup/api#opt-cssClass) - option
- [`buttons`](https://mobiscroll.com/docs/vue/popup/api#opt-buttons) - option
- [`isOpen`](https://mobiscroll.com/docs/vue/popup/api#opt-isOpen) - option
- [`responsive`](https://mobiscroll.com/docs/vue/popup/api#opt-responsive) - option
- [`close`](https://mobiscroll.com/docs/vue/popup/api#event-onClose) - event
- [`close`](https://mobiscroll.com/docs/vue/notifications/snackbar#event-onClose) - event

## Code

### SFC JS

```vue
<script setup>
import {
  formatDate,
  getJson,
  MbscButton,
  MbscEventcalendar,
  MbscInput,
  MbscPopup,
  MbscSegmented,
  MbscSegmentedGroup,
  MbscSnackbar,
  MbscTextarea,
  setOptions
} from '@mobiscroll/vue'
import { onMounted, ref } from 'vue'

setOptions({
  theme: 'ios',
  themeVariant: 'light'
})

const types = [
  {
    id: 1,
    name: 'Breakfast',
    color: '#e20f0f',
    kcal: '300 - 400 kcal',
    icon: '🍳'
  },
  {
    id: 2,
    name: 'Elevenses',
    color: '#157d13',
    kcal: '100 - 200 kcal',
    icon: '🍌'
  },
  {
    id: 3,
    name: 'Lunch',
    color: '#32a6de',
    kcal: '500 - 700 kcal',
    icon: '🍜'
  },
  {
    id: 4,
    name: 'Dinner',
    color: '#e29d1d',
    kcal: '400 - 600 kcal',
    icon: '🥙'
  },
  {
    id: 5,
    name: 'Snack',
    color: '#68169c',
    kcal: '100 - 200 kcal',
    icon: '🥨'
  }
]

const myView = {
  timeline: {
    type: 'week',
    eventDisplay: 'fill'
  }
}

const responsivePopup = {
  medium: {
    display: 'center',
    width: 400,
    fullScreen: false,
    touchUi: false,
    showOverlay: false
  }
}

const popupAddButtons = [
  'cancel',
  {
    handler: () => {
      saveEvent()
    },
    keyCode: 'enter',
    text: 'Add',
    cssClass: 'mbsc-popup-button-primary'
  }
]

const popupEditButtons = [
  'cancel',
  {
    handler: () => {
      saveEvent()
    },
    keyCode: 'enter',
    text: 'Save',
    cssClass: 'mbsc-popup-button-primary'
  }
]

const myMeals = ref([])
const tempMeal = ref(null)
const isPopupOpen = ref(false)
const isEdit = ref(false)
const name = ref('')
const calories = ref('')
const notes = ref('')
const headerPrimary = ref('')
const headerDate = ref('')
const type = ref(1)
const popupButtons = ref([])
const isSnackbarOpen = ref(false)
const snackbarButton = ref({})

function myDefaultEvent() {
  return {
    title: 'New meal',
    allDay: true
  }
}

function saveEvent() {
  tempMeal.value.title = name.value
  tempMeal.value.calories = calories.value
  tempMeal.value.notes = notes.value
  tempMeal.value.allDay = true
  tempMeal.value.resource = type.value
  if (isEdit.value) {
    // Update the event in the list
    myMeals.value = [...myMeals.value]
  } else {
    // Add the new event to the list
    myMeals.value = [...myMeals.value, tempMeal.value]
  }
  // Close the popup
  isPopupOpen.value = false
}

function loadPopupForm(event) {
  name.value = event.title
  calories.value = event.calories
  notes.value = event.notes
}

function deleteEvent(event) {
  myMeals.value = myMeals.value.filter((item) => item.id !== event.id)
  snackbarButton.value = {
    action: () => {
      myMeals.value = [...myMeals.value, event]
    },
    text: 'Undo'
  }
  isSnackbarOpen.value = true
}

function handleEventClick(args) {
  const event = args.event
  const resource = args.resourceObj
  isEdit.value = true
  tempMeal.value = event
  // Fill popup form with event data
  loadPopupForm(event)
  // Set popup options
  popupButtons.value = popupEditButtons
  headerPrimary.value = resource.name
  headerDate.value = formatDate('DDDD, DD MMMM YYYY', new Date(event.start))
  type.value = event.resource
  // Open the popup
  isPopupOpen.value = true
}

function handleEventCreated(args) {
  const event = args.event
  isEdit.value = false
  tempMeal.value = event
  // Fill popup form with event data
  loadPopupForm(event)
  // Set popup options
  popupButtons.value = popupAddButtons
  headerPrimary.value = 'New meal'
  headerDate.value = formatDate('DDDD, DD MMMM YYYY', new Date(event.start))
  type.value = event.resource
  // Open the popup
  isPopupOpen.value = true
}

function handleEventDeleted(args) {
  deleteEvent(args.event)
}

function handleDelete() {
  deleteEvent(tempMeal)
  isPopupOpen.value = false
}

function handlePopupClose() {
  isPopupOpen.value = false
}

function handleSnackbarClose() {
  isSnackbarOpen.value = false
}

onMounted(() => {
  getJson(
    'https://trial.mobiscroll.com/meal-planner/',
    (events) => {
      myMeals.value = events
    },
    'jsonp'
  )
})
</script>

<template>
  <MbscEventcalendar
    cssClass="md-meal-planner-calendar"
    :view="myView"
    :data="myMeals"
    :resources="types"
    :dragToCreate="false"
    :dragToResize="false"
    :dragToMove="true"
    :clickToCreate="true"
    :extendDefaultEvent="myDefaultEvent"
    @event-click="handleEventClick"
    @event-created="handleEventCreated"
    @event-deleted="handleEventDeleted"
  >
    <template #resource="resource">
      <div class="md-meal-planner-cont">
        <div class="md-meal-planner-title" :style="{ color: resource.color }">
          <span class="md-meal-planner-icon">{{ resource.icon }}</span>
          {{ resource.name }}
        </div>
        <div class="md-meal-planner-kcal">{{ resource.kcal }}</div>
      </div>
    </template>
    <template #timelineEventContent="args">
      <div class="md-meal-planner-event">
        <div class="md-meal-planner-event-title">{{ args.original.title }}</div>
        <div v-if="args.original.calories" class="md-meal-planner-event-desc">
          Calories {{ args.original.calories }} kcal
        </div>
      </div>
    </template>
  </MbscEventcalendar>
  <MbscPopup
    cssClass="md-meal-planner-popup"
    display="bottom"
    :fullScreen="true"
    :contentPadding="false"
    :buttons="popupButtons"
    :isOpen="isPopupOpen"
    :responsive="responsivePopup"
    @close="handlePopupClose"
  >
    <template #header>
      <div class="md-meal-planner-header-primary">{{ headerPrimary }}</div>
      <div class="md-meal-planner-header-date">{{ headerDate }}</div>
    </template>
    <MbscSegmentedGroup v-model="type">
      <template v-for="t in types" :key="t.id">
        <MbscSegmented :value="t.id">{{ t.name }}</MbscSegmented>
      </template>
    </MbscSegmentedGroup>
    <div class="mbsc-form-group">
      <MbscInput label="Name" v-model="name" />
      <MbscInput label="Calories" v-model="calories" />
      <MbscTextarea label="Notes" v-model="notes" />
    </div>
    <div v-if="isEdit" class="mbsc-button-group">
      <MbscButton
        className="mbsc-button-block"
        color="danger"
        variant="outline"
        @click="handleDelete"
      >
        Delete meal
      </MbscButton>
    </div>
  </MbscPopup>
  <MbscSnackbar
    message="Event deleted"
    :button="snackbarButton"
    :isOpen="isSnackbarOpen"
    @close="handleSnackbarClose"
  />
</template>

<style>
.md-meal-planner-header-primary {
  font-size: 16px;
  line-height: 20px;
}

.md-meal-planner-header-date {
  font-size: 14px;
  font-weight: 600;
  line-height: 20px;
  opacity: 0.6;
}

.md-meal-planner-cont {
  position: relative;
  padding-left: 34px;
  text-align: left;
}

.md-meal-planner-title {
  font-size: 20px;
}

.md-meal-planner-kcal {
  color: #929292;
}

.md-meal-planner-icon {
  position: absolute;
  left: 0;
}

.md-meal-planner-calendar.mbsc-calendar .mbsc-schedule-event-all-day-inner {
  height: 40px;
  display: flex;
  align-items: center;
}

.md-meal-planner-calendar .md-meal-planner-event-title {
  font-size: 12px;
  line-height: 18px;
}

.md-meal-planner-calendar .md-meal-planner-event-desc {
  opacity: 0.6;
  line-height: 18px;
}

.md-meal-planner-popup
  .mbsc-segmented-item:first-child
  .mbsc-selected
  .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:first-child .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:first-child .mbsc-segmented-selectbox-inner {
  background: #f08786;
}

.md-meal-planner-popup
  .mbsc-segmented-item:nth-child(2)
  .mbsc-selected
  .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(2) .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(2) .mbsc-segmented-selectbox-inner {
  background: #8abe89;
}

.md-meal-planner-popup
  .mbsc-segmented-item:nth-child(3)
  .mbsc-selected
  .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(3) .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(3) .mbsc-segmented-selectbox-inner {
  background: #99d3ef;
}

.md-meal-planner-popup
  .mbsc-segmented-item:nth-child(4)
  .mbsc-selected
  .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(4) .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(4) .mbsc-segmented-selectbox-inner {
  background: #f0ce8e;
}

.md-meal-planner-popup .mbsc-segmented-item:last-child .mbsc-selected .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:last-child .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:last-child .mbsc-segmented-selectbox-inner {
  background: #b48bce;
}
</style>
```

### SFC TS

```vue
<script setup lang="ts">
import {
  formatDate,
  getJson,
  MbscButton,
  MbscEventcalendar,
  MbscInput,
  MbscPopup,
  MbscSegmented,
  MbscSegmentedGroup,
  MbscSnackbar,
  MbscTextarea,
  setOptions
} from '@mobiscroll/vue'
import type {
  MbscCalendarEvent,
  MbscEventcalendarView,
  MbscPopupOptions,
  MbscResource,
  MbscResponsiveOptions
} from '@mobiscroll/vue'
import { onMounted, ref } from 'vue'

setOptions({
  theme: 'ios',
  themeVariant: 'light'
})

const types: MbscResource[] = [
  {
    id: 1,
    name: 'Breakfast',
    color: '#e20f0f',
    kcal: '300 - 400 kcal',
    icon: '🍳'
  },
  {
    id: 2,
    name: 'Elevenses',
    color: '#157d13',
    kcal: '100 - 200 kcal',
    icon: '🍌'
  },
  {
    id: 3,
    name: 'Lunch',
    color: '#32a6de',
    kcal: '500 - 700 kcal',
    icon: '🍜'
  },
  {
    id: 4,
    name: 'Dinner',
    color: '#e29d1d',
    kcal: '400 - 600 kcal',
    icon: '🥙'
  },
  {
    id: 5,
    name: 'Snack',
    color: '#68169c',
    kcal: '100 - 200 kcal',
    icon: '🥨'
  }
]

const myView: MbscEventcalendarView = {
  timeline: {
    type: 'week',
    eventDisplay: 'fill'
  }
}

const responsivePopup: MbscResponsiveOptions<MbscPopupOptions> = {
  medium: {
    display: 'center',
    width: 400,
    fullScreen: false,
    touchUi: false,
    showOverlay: false
  }
}

const popupAddButtons = [
  'cancel',
  {
    handler: () => {
      saveEvent()
    },
    keyCode: 'enter',
    text: 'Add',
    cssClass: 'mbsc-popup-button-primary'
  }
]

const popupEditButtons = [
  'cancel',
  {
    handler: () => {
      saveEvent()
    },
    keyCode: 'enter',
    text: 'Save',
    cssClass: 'mbsc-popup-button-primary'
  }
]

const myMeals = ref<MbscCalendarEvent[]>([])
const tempMeal = ref<MbscCalendarEvent | null>(null)
const isPopupOpen = ref<boolean>(false)
const isEdit = ref<boolean>(false)
const name = ref<string>('')
const calories = ref<string>('')
const notes = ref<string>('')
const headerPrimary = ref<string>('')
const headerDate = ref<string>('')
const type = ref<number>(1)
const popupButtons = ref<any>([])
const isSnackbarOpen = ref<boolean>(false)
const snackbarButton = ref<any>({})

function myDefaultEvent() {
  return {
    title: 'New meal',
    allDay: true
  }
}

function saveEvent() {
  tempMeal.value!.title = name.value
  tempMeal.value!.calories = calories.value
  tempMeal.value!.notes = notes.value
  tempMeal.value!.allDay = true
  tempMeal.value!.resource = type.value
  if (isEdit.value) {
    // Update the event in the list
    myMeals.value = [...myMeals.value]
  } else {
    // Add the new event to the list
    myMeals.value = [...myMeals.value, tempMeal.value!]
  }
  // Close the popup
  isPopupOpen.value = false
}

function loadPopupForm(event: MbscCalendarEvent) {
  name.value = event.title || ''
  calories.value = event.calories
  notes.value = event.notes
}

function deleteEvent(event: any) {
  myMeals.value = myMeals.value.filter((item) => item.id !== event.id)
  snackbarButton.value = {
    action: () => {
      myMeals.value = [...myMeals.value, event]
    },
    text: 'Undo'
  }
  isSnackbarOpen.value = true
}

function handleEventClick(args: any) {
  const event = args.event
  const resource = args.resourceObj
  isEdit.value = true
  tempMeal.value = event
  // Fill popup form with event data
  loadPopupForm(event)
  // Set popup options
  popupButtons.value = popupEditButtons
  headerPrimary.value = resource.name
  headerDate.value = formatDate('DDDD, DD MMMM YYYY', new Date(event.start))
  type.value = event.resource
  // Open the popup
  isPopupOpen.value = true
}

function handleEventCreated(args: any) {
  const event = args.event
  isEdit.value = false
  tempMeal.value = event
  // Fill popup form with event data
  loadPopupForm(event)
  // Set popup options
  popupButtons.value = popupAddButtons
  headerPrimary.value = 'New meal'
  headerDate.value = formatDate('DDDD, DD MMMM YYYY', new Date(event.start))
  type.value = event.resource
  // Open the popup
  isPopupOpen.value = true
}

function handleEventDeleted(args: any) {
  deleteEvent(args.event)
}

function handleDelete() {
  deleteEvent(tempMeal)
  isPopupOpen.value = false
}

function handlePopupClose() {
  isPopupOpen.value = false
}

function handleSnackbarClose() {
  isSnackbarOpen.value = false
}

onMounted(() => {
  getJson(
    'https://trial.mobiscroll.com/meal-planner/',
    (events: MbscCalendarEvent[]) => {
      myMeals.value = events
    },
    'jsonp'
  )
})
</script>

<template>
  <MbscEventcalendar
    cssClass="md-meal-planner-calendar"
    :view="myView"
    :data="myMeals"
    :resources="types"
    :dragToCreate="false"
    :dragToResize="false"
    :dragToMove="true"
    :clickToCreate="true"
    :extendDefaultEvent="myDefaultEvent"
    @event-click="handleEventClick"
    @event-created="handleEventCreated"
    @event-deleted="handleEventDeleted"
  >
    <template #resource="resource">
      <div class="md-meal-planner-cont">
        <div class="md-meal-planner-title" :style="{ color: resource.color }">
          <span class="md-meal-planner-icon">{{ resource.icon }}</span>
          {{ resource.name }}
        </div>
        <div class="md-meal-planner-kcal">{{ resource.kcal }}</div>
      </div>
    </template>
    <template #timelineEventContent="args">
      <div class="md-meal-planner-event">
        <div class="md-meal-planner-event-title">{{ args.original.title }}</div>
        <div v-if="args.original.calories" class="md-meal-planner-event-desc">
          Calories {{ args.original.calories }} kcal
        </div>
      </div>
    </template>
  </MbscEventcalendar>
  <MbscPopup
    cssClass="md-meal-planner-popup"
    display="bottom"
    :fullScreen="true"
    :contentPadding="false"
    :buttons="popupButtons"
    :isOpen="isPopupOpen"
    :responsive="responsivePopup"
    @close="handlePopupClose"
  >
    <template #header>
      <div class="md-meal-planner-header-primary">{{ headerPrimary }}</div>
      <div class="md-meal-planner-header-date">{{ headerDate }}</div>
    </template>
    <MbscSegmentedGroup v-model="type">
      <template v-for="t in types" :key="t.id">
        <MbscSegmented :value="t.id">{{ t.name }}</MbscSegmented>
      </template>
    </MbscSegmentedGroup>
    <div class="mbsc-form-group">
      <MbscInput label="Name" v-model="name" />
      <MbscInput label="Calories" v-model="calories" />
      <MbscTextarea label="Notes" v-model="notes" />
    </div>
    <div v-if="isEdit" class="mbsc-button-group">
      <MbscButton
        className="mbsc-button-block"
        color="danger"
        variant="outline"
        @click="handleDelete"
      >
        Delete meal
      </MbscButton>
    </div>
  </MbscPopup>
  <MbscSnackbar
    message="Event deleted"
    :button="snackbarButton"
    :isOpen="isSnackbarOpen"
    @close="handleSnackbarClose"
  />
</template>

<style>
.md-meal-planner-header-primary {
  font-size: 16px;
  line-height: 20px;
}

.md-meal-planner-header-date {
  font-size: 14px;
  font-weight: 600;
  line-height: 20px;
  opacity: 0.6;
}

.md-meal-planner-cont {
  position: relative;
  padding-left: 34px;
  text-align: left;
}

.md-meal-planner-title {
  font-size: 20px;
}

.md-meal-planner-kcal {
  color: #929292;
}

.md-meal-planner-icon {
  position: absolute;
  left: 0;
}

.md-meal-planner-calendar.mbsc-calendar .mbsc-schedule-event-all-day-inner {
  height: 40px;
  display: flex;
  align-items: center;
}

.md-meal-planner-calendar .md-meal-planner-event-title {
  font-size: 12px;
  line-height: 18px;
}

.md-meal-planner-calendar .md-meal-planner-event-desc {
  opacity: 0.6;
  line-height: 18px;
}

.md-meal-planner-popup
  .mbsc-segmented-item:first-child
  .mbsc-selected
  .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:first-child .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:first-child .mbsc-segmented-selectbox-inner {
  background: #f08786;
}

.md-meal-planner-popup
  .mbsc-segmented-item:nth-child(2)
  .mbsc-selected
  .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(2) .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(2) .mbsc-segmented-selectbox-inner {
  background: #8abe89;
}

.md-meal-planner-popup
  .mbsc-segmented-item:nth-child(3)
  .mbsc-selected
  .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(3) .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(3) .mbsc-segmented-selectbox-inner {
  background: #99d3ef;
}

.md-meal-planner-popup
  .mbsc-segmented-item:nth-child(4)
  .mbsc-selected
  .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(4) .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(4) .mbsc-segmented-selectbox-inner {
  background: #f0ce8e;
}

.md-meal-planner-popup .mbsc-segmented-item:last-child .mbsc-selected .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:last-child .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:last-child .mbsc-segmented-selectbox-inner {
  background: #b48bce;
}
</style>
```

## Implementation notes

- Use the timeline view with `type: 'week'` and `eventDisplay: 'fill'` so each meal event fills the full timeline cell for its day.
- Define resources for each meal type (Breakfast, Elevenses, Lunch, Dinner, Snack) with a `color`, a recommended `kcal` range, and an emoji `icon`. Resources serve as the vertical axis; no time grid is shown.
- Use `renderResource` (Angular: `resourceTemplate`, Vue: `#resource` slot) to display the meal type icon, name (in the resource color), and recommended calorie range in each resource row header.
- Use `renderTimelineEventContent` (Angular: `timelineEventContentTemplate`, Vue: `#timelineEventContent` slot) to render a custom event body showing the meal title and calorie count inside the timeline cell.
- Enable `clickToCreate` so a single click on an empty cell creates a new meal. Set `dragToMove: true` to allow moving meals to different days or resource rows. Set `dragToCreate: false` and `dragToResize: false`.
- Use `extendDefaultEvent` to pre-populate newly created events with a default title and `allDay: true`.
- Use `onEventCreated` to open the add popup. Read `args.event` to set the popup header date and derive the meal type from the event's `resource`.
- Use `onEventClick` to open the edit popup. Read `args.resourceObj.name` and format `args.event.start` with `formatDate` to populate the popup header.
- Use `onEventDeleted` to handle keyboard-triggered deletions by calling the same delete helper as the Delete button in the popup.
- Build the popup with the `Popup` component; default to `display="bottom"` with `fullScreen: true` on mobile, and use the `responsive` option to switch to `display: 'center'` at the medium breakpoint.
- Render a custom two-line popup header via the Popup's `renderHeader` prop (Angular: `headerTemplate` input on `<mbsc-popup>`, Vue: `#header` slot on `<MbscPopup>`) - the Popup's own renderer, distinct from the calendar's header renderer: a primary line showing the meal type name or "New meal", and a secondary line with the formatted date.
- Include a `SegmentedGroup` with one `Segmented` per meal type so users can change the category from within the popup. On change, update the in-progress event's `resource` to the selected ID.
- Add `Input` fields for name and calories and a `Textarea` for notes. Show a "Save" button in edit mode and an "Add" button in create mode, both alongside a Cancel action.
- For deletion, clicking the Delete button in the edit popup removes the event from state and shows a `Snackbar` with an Undo action that restores the meal.
- On popup close when the add form was canceled, refresh the events list to remove the temporary placeholder event that was added to the timeline.

## Run it in your project

1. If not signed up yet, or don't own a license, you can [start a free trial](https://mobiscroll.com/starttrial).

2. Clone the repository:

   ```
   git clone https://github.com/acidb/mobiscroll-demos-vue.git
   ```

3. If not installed yet, install the Mobiscroll CLI from NPM:

   ```
   npm install -g @mobiscroll/cli
   ```

4. Config Mobiscroll - open a terminal window in the root directory and run:

   ```
   mobiscroll config vue
   ```

5. Install dependencies - open a terminal window in the root directory and run:

   ```
   npm install
   ```

6. Run the project:

   ```
   npm run dev
   ```

On a trial, add `--trial` to the config command: `mobiscroll config vue --trial`.

Running a demo locally requires a Mobiscroll trial or a license; setup is per framework at https://mobiscroll.com/docs/vue/getting-started/installation

## Related demos

- Previous: [Restaurant Shift Scheduling](https://demo.mobiscroll.com/vue/timeline/restaurant-shift-management.md) - Build a restaurant shift planning with Vue Timeline — organize staff by role, track occupancy per shift, and toggle shifts on or off.
- Next: [Dynamic Resource Availability](https://demo.mobiscroll.com/vue/timeline/dynamically-color-and-invalidate.md) - Color-code resource rows and restrict drop zones by task type on a Vue Timeline — compatible resources turn green, incompatible ones gray during drag.
