Cally

Integrating with frameworks

How to use with React/Vue/Svelte

It is not necessary to use a framework with Cally. However, by virtue of being written as web components, Cally is framework-agnostic and can be used anywhere.

Most frameworks support web components out of the box, requiring little-to-no setup. Here we will walk through how to use Cally in React, Vue, and Svelte. The process should be similar for other frameworks.

React

As of version 19, React has excellent support for web components. They can now be used directly, with no wrappers or setup necessary.

import { useState } from "react";
import { createRoot } from 'react-dom/client';
import "cally";

function App() {
  const [value, setValue] = useState("");

  return (
    <>
      <p>Value is: {value}</p>
      <calendar-range
        value={value}
        onchange={(event) => setValue(event.target.value)}
      >
        <calendar-month />
        <calendar-month offset={1} />
      </calendar-range>
    </>
  );
}

const root = createRoot(document.getElementById("root"));
root.render(<App />)

Note: React requires that event listeners for web components use the event name verbatim. For instance if you have an event named fooBar in your web component, you must listen for the onfooBar event. This is unusual for React, which typically expects events to be like onFooBar.

TypeScript

If you are using TypeScript, you can augment React's types to add type-checking and improve your editor experience. Cally exports types for each component's props making this a simple, one-time procedure.

First you should create a d.ts file in your React project, with any name you like. For this example let's call it globals.d.ts. In that file, paste the following code:

import type {
  CalendarRangeProps,
  CalendarMonthProps,
  CalendarDateProps,
  CalendarMultiProps,
} from "cally";

type MapEvents<T> = {
  [K in keyof T as K extends `on${infer E}` ? `on${Lowercase<E>}` : K]: T[K];
};

declare module "react" {
  namespace JSX {
    interface IntrinsicElements {
      "calendar-month": MapEvents<CalendarMonthProps> &
        React.HTMLAttributes<HTMLElement>;
      "calendar-range": MapEvents<CalendarRangeProps> &
        React.HTMLAttributes<HTMLElement>;
      "calendar-date": MapEvents<CalendarDateProps> &
        React.HTMLAttributes<HTMLElement>;
      "calendar-multi": MapEvents<CalendarMultiProps> &
        React.HTMLAttributes<HTMLElement>;
    }
  }
}

This uses TypeScript's declaration merging feature. You can read more about it in the TypeScript Handbook.

Finally, you must add this to the compilerOptions.types field in your tsconfig.json:

{
  // ...
  "compilerOptions": {
    // ...
    "types": ["./globals.d.ts"]
  }
}

Vue

Vue has excellent support for web components. If you haven't already, you need to configure vue to understand web components. After that, they can be used directly.

<script setup>
import 'cally';
</script>
<template>
  <calendar-range :months="2">
    <calendar-month />
    <calendar-month :offset="1" />
  </calendar-range>
</template>

Usage with v-model

The <calendar-date> and <calendar-range> components emit change events when their value changes. You can use the v-model directive to bind refs to these events.

As noted in the Vue docs, v-model listens for input events by default. But by using the .lazy modifier, it will listen for change events.

<script setup>
import 'cally';
const selected = ref("")
</script>
<template>
  <p>Selected range: {{ selected }}</p>

  <calendar-range :months="2" v-model.lazy="selected">
    <calendar-month />
    <calendar-month :offset="1" />
  </calendar-range>
</template>

You are not required to use v-model. You can listen for events yourself if you prefer:

<script setup>
import 'cally';

const selected = ref("")
function onChange(event) {
  selected.value = event.target.value
}
</script>
<template>
  <p>Selected range: {{ selected }}</p>

  <calendar-range :months="2" :value="selected" @change="onChange">
    <calendar-month />
    <calendar-month :offset="1" />
  </calendar-range>
</template>

TypeScript

If you are using TypeScript, you can augment Vue's types to add type-checking and improve your editor experience. Cally exports types for each component's props making this a simple, one-time procedure.

First you should create a d.ts file in your Vue project, with any name you like. For this example let's call it globals.d.ts. In that file, paste the following code:

import type { DefineComponent } from "vue";
import type {
  CalendarRangeProps,
  CalendarMonthProps,
  CalendarDateProps,
} from "cally";

interface CallyComponents {
  "calendar-range": DefineComponent<CalendarRangeProps>;
  "calendar-date": DefineComponent<CalendarDateProps>;
  "calendar-month": DefineComponent<CalendarMonthProps>;
}

declare module "vue" {
  interface GlobalComponents extends CallyComponents {}
}

declare global {
  namespace JSX {
    interface IntrinsicElements extends CallyComponents {}
  }
}

This uses TypeScript's declaration merging feature. You can read more about it in the TypeScript Handbook.

Finally, you must add this to the compilerOptions.types field in your tsconfig.json:

{
  // ...
  "compilerOptions": {
    // ...
    "types": ["./globals.d.ts"]
  }
}

Now you will get type-checking for both props and events, along with auto-complete in your editor.

Svelte

Svelte has excellent support for web components. There is no setup required to start using Web Components.

<script lang="ts">
  import "cally";
</script>

<calendar-range months={2}>
  <calendar-month></calendar-month>
  <calendar-month offset={1}></calendar-month>
</calendar-range>

TypeScript

If you are using TypeScript, you can augment Svelte's types to add type-checking and improve your editor experience. Cally exports types for each component's props making this a simple, one-time procedure.

First you should create a d.ts file in your Svelte project, with any name you like. For this example let's call it globals.d.ts. In that file, paste the following code:

import type {
  CalendarRangeProps,
  CalendarMonthProps,
  CalendarDateProps,
} from "cally";

type MapEvents<T> = {
  [K in keyof T as K extends `on${infer E}` ? `on:${Lowercase<E>}` : K]: T[K];
};

declare module "svelte/elements" {
  interface SvelteHTMLElements {
    "calendar-range": MapEvents<CalendarRangeProps>;
    "calendar-month": MapEvents<CalendarMonthProps>;
    "calendar-date": MapEvents<CalendarDateProps>;
  }
}

This uses TypeScript's declaration merging feature. You can read more about it in the TypeScript Handbook.

Finally, you must add this to the compilerOptions.types field in your tsconfig.json:

{
  // ...
  "compilerOptions": {
    // ...
    "types": ["./globals.d.ts"]
  }
}

Now you will get type-checking for both props and events, along with auto-complete in your editor.