@reactleaf/calendar

Date input with calendar

Calendar input composed with @reactleaf/modal 2.0.

This example keeps a text input and a modal calendar picker synchronized to the same Temporal.PlainDate value.

Type a date directly or open the modal calendar.

Type a date and time directly or open the includeTime modal calendar.

import { Temporal } from '@js-temporal/polyfill'
import { Calendar } from '@reactleaf/calendar'
import { ModalManager, ModalProvider, type ModalComponent, useModalInstance } from '@reactleaf/modal'
import { useState } from 'react'
import '@reactleaf/calendar/index.css'
import '@reactleaf/modal/style.css'

const modal = new ModalManager()

type DatePickerModalProps = {
  initialDate: Temporal.PlainDate | null
}

const DatePickerModal: ModalComponent<DatePickerModalProps, Temporal.PlainDate | null> = ({ initialDate }) => {
  const { closeSelf } = useModalInstance<Temporal.PlainDate | null>()
  const [draftDate, setDraftDate] = useState(initialDate ?? Temporal.Now.plainDateISO())

  return (
    <div>
      <Calendar
        mode="single"
        value={draftDate}
        onSelect={(next) => {
          if (next) setDraftDate(next instanceof Temporal.PlainDateTime ? next.toPlainDate() : next)
        }}
      />
      <button type="button" onClick={() => void closeSelf(draftDate)}>
        Apply
      </button>
    </div>
  )
}

export function Example() {
  const [date, setDate] = useState<Temporal.PlainDate | null>(null)

  async function openCalendar() {
    const result = await modal.open(DatePickerModal, { initialDate: date })
    if (result !== undefined) setDate(result)
  }

  return (
    <ModalProvider manager={modal} defaultLayerOptions={{ closeDelay: 180 }} rootOptions={{ preventScroll: true }}>
      <input
        value={date?.toString() ?? ''}
        onChange={(event) => setDate(Temporal.PlainDate.from(event.target.value))}
      />
      <button type="button" onClick={openCalendar}>
        Calendar
      </button>
    </ModalProvider>
  )
}