Calendar

A calendar renders a series of pages showing the days, weeks, and months of a particular year. A single date can be selected within a calendar.

Installyarn add @diallink-corp/convergo-react-calendar
Version4.1.2
Usageimport {Calendar} from '@diallink-corp/convergo-react-calendar'

Uncontrolled Value

By default, the Calendar component handles its value uncontrolled. In the uncontrolled variant you can pass in a defaultValue to set the default date.

<Calendar defaultValue={new CalendarDate(2021, 12, 14)} />
<Calendar defaultValue={new CalendarDate(2021, 12, 14)} />
<Calendar
  defaultValue={new CalendarDate(
    2021,
    12,
    14
  )}
/>

Controlled Value

You can also handle the value of the component in a controlled manner. To do so, you can pass in a value prop as well as an onChange handler to modify the value when the user changes the date.

function Example() {
  const [date, setDate] = useState(new CalendarDate(1997, 10, 14));

  return (
    <Calendar value={date} onChange={setDate} />
  );
}
function Example() {
  const [date, setDate] = useState(
    new CalendarDate(1997, 10, 14)
  );

  return <Calendar value={date} onChange={setDate} />;
}
function Example() {
  const [date, setDate] =
    useState(
      new CalendarDate(
        1997,
        10,
        14
      )
    );

  return (
    <Calendar
      value={date}
      onChange={setDate}
    />
  );
}

Calendar and Locale

The calendar component supports most calendar types across the globe as well as all browser locales for accessibility.

function Example() {
  const calendars = [
    { key: 'gregory', name: 'Gregorian' },
    { key: 'japanese', name: 'Japanese' },
    { key: 'buddhist', name: 'Buddhist' },
    { key: 'roc', name: 'Taiwan' },
    { key: 'persian', name: 'Persian' },
    { key: 'indian', name: 'Indian' },
    { key: 'islamic-umalqura', name: 'Islamic (Umm al-Qura)' },
    { key: 'islamic-civil', name: 'Islamic Civil' },
    { key: 'islamic-tbla', name: 'Islamic Tabular' },
    { key: 'hebrew', name: 'Hebrew' },
    { key: 'coptic', name: 'Coptic' },
    { key: 'ethiopic', name: 'Ethiopic' },
    { key: 'ethioaa', name: 'Ethiopic (Amete Alem)' }
  ];
  const [calendar, setCalendar] = useState(calendars[0].key);

  // https://github.com/unicode-org/cldr/blob/22af90ae3bb04263f651323ce3d9a71747a75ffb/common/supplemental/supplementalData.xml#L4649-L4664
  const locales = [
    { label: 'Default', locale: 'en-US', ordering: 'gregory' },
    {
      label: 'Arabic (Algeria)',
      locale: 'ar-DZ',
      territories: 'DJ DZ EH ER IQ JO KM LB LY MA MR OM PS SD SY TD TN YE',
      ordering: 'gregory islamic islamic-civil islamic-tbla'
    },
    {
      label: 'Arabic (United Arab Emirates)',
      locale: 'ar-AE',
      territories: 'AE BH KW QA',
      ordering: 'gregory islamic-umalqura islamic islamic-civil islamic-tbla'
    },
    {
      label: 'Arabic (Egypt)',
      locale: 'AR-EG',
      territories: 'EG',
      ordering: 'gregory coptic islamic islamic-civil islamic-tbla'
    },
    {
      label: 'Arabic (Saudi Arabia)',
      locale: 'ar-SA',
      territories: 'SA',
      ordering: 'islamic-umalqura gregory islamic islamic-rgsa'
    },
    {
      label: 'Farsi (Afghanistan)',
      locale: 'fa-AF',
      territories: 'AF IR',
      ordering: 'persian gregory islamic islamic-civil islamic-tbla'
    },
    {
      label: 'Amharic (Ethiopia)',
      locale: 'am-ET',
      territories: 'ET',
      ordering: 'gregory ethiopic ethioaa'
    },
    {
      label: 'Hebrew (Israel)',
      locale: 'he-IL',
      territories: 'IL',
      ordering: 'gregory hebrew islamic islamic-civil islamic-tbla'
    },
    {
      label: 'Hindi (India)',
      locale: 'hi-IN',
      territories: 'IN',
      ordering: 'gregory indian'
    },
    {
      label: 'Japanese (Japan)',
      locale: 'ja-JP',
      territories: 'JP',
      ordering: 'gregory japanese'
    },
    {
      label: 'Thai (Thailand)',
      locale: 'th-TH',
      territories: 'TH',
      ordering: 'buddhist gregory'
    },
    {
      label: 'Chinese (Taiwan)',
      locale: 'zh-TW',
      territories: 'TW',
      ordering: 'gregory roc chinese'
    }
  ];
  const { locale: defaultLocale } = useLocale();
  const [locale, setLocale] = useState(defaultLocale);

  const [date, setDate] = useState(new CalendarDate(2021, 12, 8));

  const handleLocaleChange = (locale) => {
    setLocale(locale);
    const fullLocale = locales.find((p) => p.locale === locale);
    setCalendar(fullLocale.ordering.split(' ')[0]);
  };

  return (
    <div>
      <Select
        label="Locale"
        items={locales}
        selectedKey={locale}
        onSelectionChange={handleLocaleChange}
      >
        {(item) => <Item key={item.locale}>{item.label}</Item>}
      </Select>
      <Select
        label="Calendar"
        items={calendars}
        selectedKey={calendar}
        onSelectionChange={setCalendar}
      >
        {(item) => <Item key={item.key}>{item.name}</Item>}
      </Select>
      <ConvergoProvider locale={`${locale}${calendar && `-u-ca-${calendar}`}`}>
        <Calendar value={date} onChange={setDate} />
      </ConvergoProvider>
    </div>
  );
}
function Example() {
  const calendars = [
    { key: 'gregory', name: 'Gregorian' },
    { key: 'japanese', name: 'Japanese' },
    { key: 'buddhist', name: 'Buddhist' },
    { key: 'roc', name: 'Taiwan' },
    { key: 'persian', name: 'Persian' },
    { key: 'indian', name: 'Indian' },
    {
      key: 'islamic-umalqura',
      name: 'Islamic (Umm al-Qura)'
    },
    { key: 'islamic-civil', name: 'Islamic Civil' },
    { key: 'islamic-tbla', name: 'Islamic Tabular' },
    { key: 'hebrew', name: 'Hebrew' },
    { key: 'coptic', name: 'Coptic' },
    { key: 'ethiopic', name: 'Ethiopic' },
    { key: 'ethioaa', name: 'Ethiopic (Amete Alem)' }
  ];
  const [calendar, setCalendar] = useState(
    calendars[0].key
  );

  // https://github.com/unicode-org/cldr/blob/22af90ae3bb04263f651323ce3d9a71747a75ffb/common/supplemental/supplementalData.xml#L4649-L4664
  const locales = [
    {
      label: 'Default',
      locale: 'en-US',
      ordering: 'gregory'
    },
    {
      label: 'Arabic (Algeria)',
      locale: 'ar-DZ',
      territories:
        'DJ DZ EH ER IQ JO KM LB LY MA MR OM PS SD SY TD TN YE',
      ordering: 'gregory islamic islamic-civil islamic-tbla'
    },
    {
      label: 'Arabic (United Arab Emirates)',
      locale: 'ar-AE',
      territories: 'AE BH KW QA',
      ordering:
        'gregory islamic-umalqura islamic islamic-civil islamic-tbla'
    },
    {
      label: 'Arabic (Egypt)',
      locale: 'AR-EG',
      territories: 'EG',
      ordering:
        'gregory coptic islamic islamic-civil islamic-tbla'
    },
    {
      label: 'Arabic (Saudi Arabia)',
      locale: 'ar-SA',
      territories: 'SA',
      ordering:
        'islamic-umalqura gregory islamic islamic-rgsa'
    },
    {
      label: 'Farsi (Afghanistan)',
      locale: 'fa-AF',
      territories: 'AF IR',
      ordering:
        'persian gregory islamic islamic-civil islamic-tbla'
    },
    {
      label: 'Amharic (Ethiopia)',
      locale: 'am-ET',
      territories: 'ET',
      ordering: 'gregory ethiopic ethioaa'
    },
    {
      label: 'Hebrew (Israel)',
      locale: 'he-IL',
      territories: 'IL',
      ordering:
        'gregory hebrew islamic islamic-civil islamic-tbla'
    },
    {
      label: 'Hindi (India)',
      locale: 'hi-IN',
      territories: 'IN',
      ordering: 'gregory indian'
    },
    {
      label: 'Japanese (Japan)',
      locale: 'ja-JP',
      territories: 'JP',
      ordering: 'gregory japanese'
    },
    {
      label: 'Thai (Thailand)',
      locale: 'th-TH',
      territories: 'TH',
      ordering: 'buddhist gregory'
    },
    {
      label: 'Chinese (Taiwan)',
      locale: 'zh-TW',
      territories: 'TW',
      ordering: 'gregory roc chinese'
    }
  ];
  const { locale: defaultLocale } = useLocale();
  const [locale, setLocale] = useState(defaultLocale);

  const [date, setDate] = useState(
    new CalendarDate(2021, 12, 8)
  );

  const handleLocaleChange = (locale) => {
    setLocale(locale);
    const fullLocale = locales.find((p) =>
      p.locale === locale
    );
    setCalendar(fullLocale.ordering.split(' ')[0]);
  };

  return (
    <div>
      <Select
        label="Locale"
        items={locales}
        selectedKey={locale}
        onSelectionChange={handleLocaleChange}
      >
        {(item) => (
          <Item key={item.locale}>{item.label}</Item>
        )}
      </Select>
      <Select
        label="Calendar"
        items={calendars}
        selectedKey={calendar}
        onSelectionChange={setCalendar}
      >
        {(item) => <Item key={item.key}>{item.name}</Item>}
      </Select>
      <ConvergoProvider
        locale={`${locale}${
          calendar && `-u-ca-${calendar}`
        }`}
      >
        <Calendar value={date} onChange={setDate} />
      </ConvergoProvider>
    </div>
  );
}
function Example() {
  const calendars = [
    {
      key: 'gregory',
      name: 'Gregorian'
    },
    {
      key: 'japanese',
      name: 'Japanese'
    },
    {
      key: 'buddhist',
      name: 'Buddhist'
    },
    {
      key: 'roc',
      name: 'Taiwan'
    },
    {
      key: 'persian',
      name: 'Persian'
    },
    {
      key: 'indian',
      name: 'Indian'
    },
    {
      key:
        'islamic-umalqura',
      name:
        'Islamic (Umm al-Qura)'
    },
    {
      key:
        'islamic-civil',
      name:
        'Islamic Civil'
    },
    {
      key:
        'islamic-tbla',
      name:
        'Islamic Tabular'
    },
    {
      key: 'hebrew',
      name: 'Hebrew'
    },
    {
      key: 'coptic',
      name: 'Coptic'
    },
    {
      key: 'ethiopic',
      name: 'Ethiopic'
    },
    {
      key: 'ethioaa',
      name:
        'Ethiopic (Amete Alem)'
    }
  ];
  const [
    calendar,
    setCalendar
  ] = useState(
    calendars[0].key
  );

  // https://github.com/unicode-org/cldr/blob/22af90ae3bb04263f651323ce3d9a71747a75ffb/common/supplemental/supplementalData.xml#L4649-L4664
  const locales = [
    {
      label: 'Default',
      locale: 'en-US',
      ordering: 'gregory'
    },
    {
      label:
        'Arabic (Algeria)',
      locale: 'ar-DZ',
      territories:
        'DJ DZ EH ER IQ JO KM LB LY MA MR OM PS SD SY TD TN YE',
      ordering:
        'gregory islamic islamic-civil islamic-tbla'
    },
    {
      label:
        'Arabic (United Arab Emirates)',
      locale: 'ar-AE',
      territories:
        'AE BH KW QA',
      ordering:
        'gregory islamic-umalqura islamic islamic-civil islamic-tbla'
    },
    {
      label:
        'Arabic (Egypt)',
      locale: 'AR-EG',
      territories: 'EG',
      ordering:
        'gregory coptic islamic islamic-civil islamic-tbla'
    },
    {
      label:
        'Arabic (Saudi Arabia)',
      locale: 'ar-SA',
      territories: 'SA',
      ordering:
        'islamic-umalqura gregory islamic islamic-rgsa'
    },
    {
      label:
        'Farsi (Afghanistan)',
      locale: 'fa-AF',
      territories:
        'AF IR',
      ordering:
        'persian gregory islamic islamic-civil islamic-tbla'
    },
    {
      label:
        'Amharic (Ethiopia)',
      locale: 'am-ET',
      territories: 'ET',
      ordering:
        'gregory ethiopic ethioaa'
    },
    {
      label:
        'Hebrew (Israel)',
      locale: 'he-IL',
      territories: 'IL',
      ordering:
        'gregory hebrew islamic islamic-civil islamic-tbla'
    },
    {
      label:
        'Hindi (India)',
      locale: 'hi-IN',
      territories: 'IN',
      ordering:
        'gregory indian'
    },
    {
      label:
        'Japanese (Japan)',
      locale: 'ja-JP',
      territories: 'JP',
      ordering:
        'gregory japanese'
    },
    {
      label:
        'Thai (Thailand)',
      locale: 'th-TH',
      territories: 'TH',
      ordering:
        'buddhist gregory'
    },
    {
      label:
        'Chinese (Taiwan)',
      locale: 'zh-TW',
      territories: 'TW',
      ordering:
        'gregory roc chinese'
    }
  ];
  const {
    locale: defaultLocale
  } = useLocale();
  const [
    locale,
    setLocale
  ] = useState(
    defaultLocale
  );

  const [date, setDate] =
    useState(
      new CalendarDate(
        2021,
        12,
        8
      )
    );

  const handleLocaleChange =
    (locale) => {
      setLocale(locale);
      const fullLocale =
        locales.find(
          (p) =>
            p.locale ===
              locale
        );
      setCalendar(
        fullLocale
          .ordering
          .split(' ')[0]
      );
    };

  return (
    <div>
      <Select
        label="Locale"
        items={locales}
        selectedKey={locale}
        onSelectionChange={handleLocaleChange}
      >
        {(item) => (
          <Item
            key={item
              .locale}
          >
            {item.label}
          </Item>
        )}
      </Select>
      <Select
        label="Calendar"
        items={calendars}
        selectedKey={calendar}
        onSelectionChange={setCalendar}
      >
        {(item) => (
          <Item
            key={item
              .key}
          >
            {item.name}
          </Item>
        )}
      </Select>
      <ConvergoProvider
        locale={`${locale}${
          calendar &&
          `-u-ca-${calendar}`
        }`}
      >
        <Calendar
          value={date}
          onChange={setDate}
        />
      </ConvergoProvider>
    </div>
  );
}

Min and Max Value

You can specify a minimum and maximum date that the user can select.

<Calendar 
  defaultValue={new CalendarDate(2019, 2, 10)} 
  minValue={new CalendarDate(2019, 2, 5)} 
  maxValue={new CalendarDate(2019, 2, 15)}
/>
<Calendar 
  defaultValue={new CalendarDate(2019, 2, 10)} 
  minValue={new CalendarDate(2019, 2, 5)} 
  maxValue={new CalendarDate(2019, 2, 15)}
/>
<Calendar
  defaultValue={new CalendarDate(
    2019,
    2,
    10
  )}
  minValue={new CalendarDate(
    2019,
    2,
    5
  )}
  maxValue={new CalendarDate(
    2019,
    2,
    15
  )}
/>

Visible Months

You can define how many months should be visible through the visibleMonths prop.

<Calendar defaultValue={new CalendarDate(2019, 6, 5)} visibleMonths={3} />
<Calendar
  defaultValue={new CalendarDate(2019, 6, 5)}
  visibleMonths={3}
/>
<Calendar
  defaultValue={new CalendarDate(
    2019,
    6,
    5
  )}
  visibleMonths={3}
/>

Disabled

The Calendar component can be disabled via the isDisabled prop.

<Calendar defaultValue={new CalendarDate(2019, 6, 5)} isDisabled />
<Calendar
  defaultValue={new CalendarDate(2019, 6, 5)}
  isDisabled
/>
<Calendar
  defaultValue={new CalendarDate(
    2019,
    6,
    5
  )}
  isDisabled
/>

Unavailable Dates

The Calendar component allows to mark certain dates as unavailable.

function Example() {
  const now = today(getLocalTimeZone());
  const disabledRanges = [
    [now, now.add({ days: 5 })],
    [now.add({ days: 14 }), now.add({ days: 16 })],
    [now.add({ days: 23 }), now.add({ days: 24 })]
  ];

  const { locale } = useLocale();
  const isDateUnavailable = (date) =>
    isWeekend(date, locale) ||
    disabledRanges.some((interval) =>
      date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0
    );

  return (
    <Calendar
      aria-label="Meeting date"
      minValue={today(getLocalTimeZone())}
      isDateUnavailable={isDateUnavailable}
    />
  );
}
function Example() {
  const now = today(getLocalTimeZone());
  const disabledRanges = [
    [now, now.add({ days: 5 })],
    [now.add({ days: 14 }), now.add({ days: 16 })],
    [now.add({ days: 23 }), now.add({ days: 24 })]
  ];

  const { locale } = useLocale();
  const isDateUnavailable = (date) =>
    isWeekend(date, locale) ||
    disabledRanges.some((interval) =>
      date.compare(interval[0]) >= 0 &&
      date.compare(interval[1]) <= 0
    );

  return (
    <Calendar
      aria-label="Meeting date"
      minValue={today(getLocalTimeZone())}
      isDateUnavailable={isDateUnavailable}
    />
  );
}
function Example() {
  const now = today(
    getLocalTimeZone()
  );
  const disabledRanges =
    [
      [
        now,
        now.add({
          days: 5
        })
      ],
      [
        now.add({
          days: 14
        }),
        now.add({
          days: 16
        })
      ],
      [
        now.add({
          days: 23
        }),
        now.add({
          days: 24
        })
      ]
    ];

  const { locale } =
    useLocale();
  const isDateUnavailable =
    (date) =>
      isWeekend(
        date,
        locale
      ) ||
      disabledRanges
        .some((
          interval
        ) =>
          date.compare(
              interval[0]
            ) >= 0 &&
          date.compare(
              interval[1]
            ) <= 0
        );

  return (
    <Calendar
      aria-label="Meeting date"
      minValue={today(
        getLocalTimeZone()
      )}
      isDateUnavailable={isDateUnavailable}
    />
  );
}

Read Only

The Calendar component can be marked as read only via the isReadOnly prop.

<Calendar defaultValue={new CalendarDate(2019, 6, 5)} isReadOnly />
<Calendar
  defaultValue={new CalendarDate(2019, 6, 5)}
  isReadOnly
/>
<Calendar
  defaultValue={new CalendarDate(
    2019,
    6,
    5
  )}
  isReadOnly
/>

API