Build a calendar app in Miro

Use the Miro Web SDK to build a calendar app for Miro boards.

The Miro Developer Platform allows developers to create apps and integrations on top of Miro that add extra or custom functionality.

Whether it's something creative, useful, or something that allows you and your team to collaborate more efficiently together, Miro’s Developer Platform gives you all the pieces you need to build your next big idea.

Goal

In this guide, we’ll be exploring how to create a Calendar app in Miro using Miro’s Web SDK. This app allows you to create a calendar consisting of shapes and text for the given month and year you’ve selected.

Here’s what it will look when we’re done:

Final app working

Get started

We’ll be creating this app from the create-miro-app tool, which gives us a starting point with everything we need to build. Additionally, we’ll be creating this app in React and Typescript.

To start, open a terminal session and create a new app by running the following command:

npx create-miro-app

Select the prompts for React, and then Typescript.

This will give us a starting template to start building our app with.

After we’ve got this up and running, we’ll need to set up our developer team in Miro.

Setup

To create apps in Miro, you must first create a developer team. This is a team that allows you to test and build your app with_out having to worry about it messing up any projects on your main team.

After you create a developer team in Miro, you’ll be able to start building apps right from the dashboard.

  1. Navigate to your developer team’s dashboard, and click “Create app” in the upper right corner. From here we’ll need to fill in information about our app.
  2. The most important detail we need to fill in is our App URL. This is a URL that points to our running app.
    As we develop our app, we’ll be developing on localhost:3000; make sure you put http://localhost:3000 as your App URL to enable local hosting development of the app.
    To run your app locally, your web browser must allow the HTTP transport protocol.
    Recommended web browser: Google Chrome
    Apple Safari doesn't allow HTTP; therefore, it doesn't allow the app to run locally.
  3. You can access your apps from your user dashboard if you ever want to update this in the future.
  4. After we save our settings here, we can verify it’s working by creating a new project in our developer team, and launching the app from the sidebar

If you don’t see your app appear, make sure your app is running by running npm start in your project directory and checking again.

After we see the app in Miro, we’re ready to start building!

Project structure

Let’s take a look at the project structure and some important files we’ll use.

File Description
app.tsx This file contains the structure and logic for our app. We’ll be creating the app’s UI in this file, and calling the Miro SDK to manipulate the board.
index.ts This file contains the code to initialize the app when opened from the sidebar.
app.html Miro requires an HTML file in order to work. This file contains the main structure of the app, but since we’re using React, the majority of the code will be located in App.tsx.
assets/style.css The main styles for our app. We’re also going to be using Mirotone for adding elements, but any custom styles can be added here as well. Mirotone is simple to use, and contains a lightweight library of components that match Miro’s UI styles.

Build the UI

Before we add any logic to our app, we’re going to need some structure for the app in the sidebar. This is what it will look like.

Calendar app UI

Our calendar app will enable users to choose a month and a year from a dropdown, and hit a button to insert a calendar onto the board.

To start, we’ll add 2 <select> components, and a <button>. We can automatically add class names to the elements to match them with Miro’s style.

You can copy the code snippet below into app.tsx to see the structure we’ll use. Make sure to replace the existing code from our template, which starts on line 18 and ends on line 39.

You might see some errors at first, but we’ll fix these in the next step.

<div className="grid" style={{ height: 'auto', width: "100%" }}>
            <div className="cs1 ce12">
                <p className="p-medium">Select the month and year, and click "Insert Calendar" to add an editable calendar to the board.</p>
            </div>
            <div className="cs1 ce12">
                <h4 className="h4">Month</h4>
                <select className="select" value={month} onChange={handleMonthChange}>
                    <option value="1">January</option>
                    <option value="2">February</option>
                    <option value="3">March</option>
                    <option value="4">April</option>
                    <option value="5">May</option>
                    <option value="6">June</option>
                    <option value="7">July</option>
                    <option value="8">August</option>
                    <option value="9">September</option>
                    <option value="10">October</option>
                    <option value="11">November</option>
                    <option value="12">December</option>
                </select>
            </div>
            <div className="cs1 ce12">
                <h4 className="h4">Year</h4>
                <select className="select" value={year} onChange={handleYearChange}>
                    <option value="2010">2010</option>
                    <option value="2011">2011</option>
                    <option value="2012">2012</option>
                    <option value="2013">2013</option>
                    <option value="2014">2014</option>
                    <option value="2015">2015</option>
                    <option value="2016">2016</option>
                    <option value="2017">2017</option>
                    <option value="2018">2018</option>
                    <option value="2019">2019</option>
                    <option value="2020">2020</option>
                    <option value="2021">2021</option>
                    <option value="2022">2022</option>
                    <option value="2023">2023</option>
                    <option value="2024">2024</option>
                    <option value="2025">2025</option>
                    <option value="2026">2026</option>
                    <option value="2027">2027</option>
                    <option value="2028">2028</option>
                    <option value="2029">2029</option>
                    <option value="2030">2030</option>
                </select>
            </div>
            <div className="cs1 ce12">
                <button className="button button-primary" onClick={handleInsertCalendar}>Insert Calendar</button>

            </div>
        </div>

When we open the app again, we should see our two dropdowns and a button. Now that we have the structure, we just need to add a few functions to capture the user’s selection, so we can use it later.

Since we’re using React, we can use a hook to hold the user's month and year selection. I won't go into too much detail in this guide, but React has a great overview on how state hooks work.

Copy and paste the following code into the top of app.tsx, below the main body of the app, under line 12.

    // @ts-ignore
    const { board } = window.miro;

    const d = new Date();
    let currentYear = d.getFullYear();
    let currentMonth = d.getMonth() + 1;

    const [month, setMonth] = React.useState(currentMonth)
    const [year, setYear] = React.useState(currentYear)

    const handleMonthChange = (e: ChangeEvent<HTMLSelectElement>) => {
      setMonth(e.target.value)
    }

    const handleYearChange = (e: ChangeEvent<HTMLSelectElement>) => {
      setYear(e.target.value)
    }

These functions allow us to keep the state of which month and year the user has selected. I’ve also added 3 lines at the top, which will give us some default values for our app. These 3 lines will give us the current year and month the user is in when the app is opened, giving a much better user experience for someone using this app down the road.

So now we’ve gotten the structure for our app, we can move onto adding the logic that will generate the days for our calendar.

Build the calendar logic

The section below gives an overview on how our calendar days are generated.

We’re going to be building a function that returns an array of arrays that contain numbers. If that sounds confusing, this might help. If the current month and year is March 2022, the array returned would look like this:

Calendar view

Figure 3. March 2022 Calendar.

Array view

[
  [0, 1, 2, 3, 4, 5, 6],
  [7, 8, 9, 10, 11, 12, 13],
  [14, 15, 16, 17, 18, 19, 20],
  [21, 22, 23, 24, 25, 26, 27],
  [28, 29, 30, 31, 0 , 0, 0],
  [0, 0, 0, 0, 0, 0, 0]
]

See how similar the two look? We can use this structure to then generate the shapes on our board when we click the insert button.

Create a new file called useCalendar.ts in the src folder, and copy/paste the code below into it.

const getDaysInMonth = (month: number, year: number) => {
  return new Date(year, month, 0).getDate();
};

const getStartingDayOfMonth = (month: number, year: number) => {
  return new Date(year + "-" + month + "-01").getDay();
};

export const useCalendar = (month: number, year: number) => {
  const numberOfDays = getDaysInMonth(month, year);
  const startingDay = getStartingDayOfMonth(month, year);

  let startingIndex: number = startingDay

  if(startingDay !== 0) {
    startingIndex = startingDay - 1
  }
  
  let currentWeek = 0;
  let currentWeekIndex = 0;
  const dayArray = Array.from(
    { length: numberOfDays + startingIndex },
    (_, i) => (i < startingIndex ? 0 : i + (1 - startingIndex))
  );

  const calendar = new Array(6);

  for (let i = 0; i < calendar.length; i++) {
    calendar[i] = Array.from(Array(7), () => 0);
  }

  dayArray.map((day) => {
    calendar[currentWeek][currentWeekIndex] = day;

    if (currentWeekIndex === 6) {
      currentWeek++;
    }

    if (currentWeekIndex < 6) {
      currentWeekIndex++;
    } else {
      currentWeekIndex = 0;
    }
  });

  return calendar;
};

The first two functions are helpers, to give us the number of days in a given month, and the starting day of that same month.

We’re then exporting a function that we can use in app.tsx, that gives us the calendar array structure mentioned above.

Now that we have a way to generate a calendar and a way to capture our users’ year and month selection, we just need to tap into the Miro SDK to generate our shapes on the board.

Use the Miro Web SDK

Heading back to app.tsx, we now need to import our newly created useCalendar function into this file. At the very top of this file, add the following line.

import { useCalendar } from "./useCalendar"

We’re now able to call this function using the stored input state from our user. We’re going to make a function that fires when the “Insert Calendar” button is clicked, which is the last thing we’ll need to add.

Add in the following code snippet towards the top of this app.tsx, underneath the handleYearChange() function.

   const handleInsertCalendar = async () => {
        const calendar = useCalendar(month, year)

        calendar.map((monthRow: number[], index: number) => {
            const ySpacing = (index * 120)

            monthRow.map(async (day: number, index: number) => {
                const xSpacing = (index * 120)

                await board.createShape({
                    content: day === 0 ? `` : `<b>${day.toString()}</b>`,
                    shape: 'round_rectangle',
                    style: {
                        fillColor: '#ffffff',
                    },
                    x: xSpacing,
                    y: ySpacing,
                    width: 100,
                    height: 100,
                });

            })

        })
    }

This function will generate the calendar array mentioned in the previous step. After it generates the calendar, it maps over the days, and generates a shape for each day of the month.

We generate the shape by calling createShape from Miro’s SDK.

We can pass in a few extra parameters to control the style of the calendar day (like height, width, backgroundColor, and more), but the most important one we’re working with is the properties x and y.

By default, inserting an element onto the board will be added at the boards center, or coordinates 0, 0.

If we don’t update this, all of the calendar days would be created on top of each other!

Taking a look at our handleInsertCalendar() function, we can see a few lines of code that help us space our shapes evenly into a grid.

Because our useCalendar function we made in the last step gives us an array of days back, we can call a method called map() on the array it returns. This will iterate through every day, and allow us to configure a few extra pieces before we actually create the shape for the day.

We’re adding in both a xSpacing and ySpacing variable that updates based on the index of the day we’re currently on, and multiplies it by 120. We’re choosing 120 because the size of the shape we’re creating is a 100 x 100 square, meaning we’ll have 20 pixels of space around the edges.

We’re then passing these variables to the x and y properties in the createShape call, wrapping up the main calendar days.

Let's wrap up

I’ve also added in 2 extra parts to make our calendar look a bit nicer—The days of the week, and a title for our calendar. You can find the finished version here.

These use the same logic and SDK code we looked at before to position and add the elements where we need them.

The Miro Developer Platform allows you to create all kinds of fun, useful apps and integrations. It’s flexible enough to let you build what you want, while being powerful enough to support essential workflows for you or your team.

We have many more examples of other apps and ideas you can build: check Miro App Examples to find more.


What's next

Learn how to deploy your Miro app.