{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"calendar","type":"registry:ui","title":"Calendar","description":"Month grid for a single date or a range, one or two months wide; months slide in with a blur in the direction of travel and the range band sweeps out from the start day.","author":"GetUrUI <hello@geturui.dev>","dependencies":["motion","lucide-react"],"registryDependencies":[],"files":[{"path":"registry/geturui/ui/calendar.tsx","type":"registry:ui","target":"components/ui/calendar.tsx","content":"\"use client\";\n\nimport * as React from \"react\";\nimport { AnimatePresence, motion, useReducedMotion, type Variants } from \"motion/react\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\n/* ────────────────────────────────────────────────────────────────────────────\n * Date math. Everything works on local calendar days (midnight), never on the\n * clock, so the same props render the same grid on the server and the client.\n * ──────────────────────────────────────────────────────────────────────────── */\n\nexport interface DateRange {\n  start: Date | null;\n  end: Date | null;\n}\n\nexport type WeekStart = 0 | 1 | 2 | 3 | 4 | 5 | 6;\n\nexport const MONTH_NAMES = [\n  \"January\",\n  \"February\",\n  \"March\",\n  \"April\",\n  \"May\",\n  \"June\",\n  \"July\",\n  \"August\",\n  \"September\",\n  \"October\",\n  \"November\",\n  \"December\",\n];\nexport const WEEKDAY_NAMES = [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"];\n\n/** The same calendar day at local midnight. */\nexport const startOfDay = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate());\n/** First day of the month containing `d`. */\nexport const startOfMonth = (d: Date) => new Date(d.getFullYear(), d.getMonth(), 1);\nexport const daysInMonth = (year: number, month: number) => new Date(year, month + 1, 0).getDate();\nexport const addDays = (d: Date, n: number) => new Date(d.getFullYear(), d.getMonth(), d.getDate() + n);\n/** Adds months, clamping the day so Jan 31 + 1 month is Feb 28/29. */\nexport const addMonths = (d: Date, n: number) => {\n  const y = d.getFullYear();\n  const m = d.getMonth() + n;\n  return new Date(y, m, Math.min(d.getDate(), daysInMonth(y, m)));\n};\n/** Negative when `a` is an earlier day than `b`, 0 on the same day. */\nexport const compareDays = (a: Date, b: Date) =>\n  a.getFullYear() - b.getFullYear() || a.getMonth() - b.getMonth() || a.getDate() - b.getDate();\nexport const isSameDay = (a?: Date | null, b?: Date | null) => !!a && !!b && compareDays(a, b) === 0;\nexport const isSameMonth = (a: Date, b: Date) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();\n/** Whole days from `a` to `b` (DST safe). */\nexport const daysBetween = (a: Date, b: Date) => Math.round((startOfDay(b).getTime() - startOfDay(a).getTime()) / 86_400_000);\nexport const monthsBetween = (a: Date, b: Date) => (b.getFullYear() - a.getFullYear()) * 12 + b.getMonth() - a.getMonth();\n/** YYYY-MM-DD in local time. */\nexport const toISODate = (d: Date) =>\n  `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, \"0\")}-${String(d.getDate()).padStart(2, \"0\")}`;\n/** Parses YYYY-MM-DD as a local day; null when malformed. */\nexport function parseISODate(s: string | null | undefined): Date | null {\n  const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(s ?? \"\");\n  if (!m) return null;\n  const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));\n  return d.getMonth() === Number(m[2]) - 1 ? d : null;\n}\n\n/** \"Oct 5\" · \"Oct 5, 2026\" · \"Monday, October 5, 2026\". Locale-free, so it never mismatches on hydration. */\nexport function formatDate(d: Date, style: \"short\" | \"medium\" | \"long\" = \"medium\") {\n  if (style === \"long\") return `${WEEKDAY_NAMES[d.getDay()]}, ${MONTH_NAMES[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`;\n  const s = `${MONTH_NAMES[d.getMonth()].slice(0, 3)} ${d.getDate()}`;\n  return style === \"short\" ? s : `${s}, ${d.getFullYear()}`;\n}\n\n/** \"Oct 1 – 7, 2026\", \"Sep 28 – Oct 4, 2026\" or \"Dec 29, 2026 – Jan 4, 2027\". */\nexport function formatRange(r: DateRange) {\n  const { start, end } = r;\n  if (!start) return \"\";\n  if (!end) return `${formatDate(start)} – …`;\n  if (isSameDay(start, end)) return formatDate(start);\n  if (start.getFullYear() !== end.getFullYear()) return `${formatDate(start)} – ${formatDate(end)}`;\n  if (isSameMonth(start, end)) return `${formatDate(start, \"short\")} – ${end.getDate()}, ${end.getFullYear()}`;\n  return `${formatDate(start, \"short\")} – ${formatDate(end)}`;\n}\n\nconst subscribeNever = () => () => {};\nconst readToday = () => toISODate(new Date());\n\n/**\n * Today as a local day, hydration safe: returns the passed date, otherwise null during SSR and the\n * first client render, then the real day. Pass a fixed date in demos and tests.\n */\nexport function useToday(today?: Date | null): Date | null {\n  const key = React.useSyncExternalStore(subscribeNever, readToday, () => \"\");\n  if (today) return startOfDay(today);\n  return parseISODate(key);\n}\n\n/** Six fixed weeks covering `month`, so every page has the same height. */\nexport function monthMatrix(month: Date, weekStartsOn: WeekStart = 0): Date[][] {\n  const first = startOfMonth(month);\n  const lead = (first.getDay() - weekStartsOn + 7) % 7;\n  const start = addDays(first, -lead);\n  return Array.from({ length: 6 }, (_, w) => Array.from({ length: 7 }, (_, i) => addDays(start, w * 7 + i)));\n}\n\n/* ──────────────────────────────────────────────────────────────────────────── */\n\ninterface CalendarBaseProps {\n  /** The day marked as today. Omit to read the clock after hydration. */\n  today?: Date;\n  /** Earliest selectable day. */\n  min?: Date;\n  /** Latest selectable day. */\n  max?: Date;\n  /** Days that cannot be picked. */\n  disabledDates?: Date[];\n  /** Return true to disable a day (weekends, booked nights). */\n  isDateDisabled?: (date: Date) => boolean;\n  /** Days that get a small dot (deadlines, events). */\n  highlightedDates?: Date[];\n  /** 0 = Sunday … 6 = Saturday. @default 0 */\n  weekStartsOn?: WeekStart;\n  /** Months shown side by side. @default 1 */\n  numberOfMonths?: 1 | 2;\n  /** Show adjacent-month days in the grid. @default true for one month */\n  showOutsideDays?: boolean;\n  /** Controlled first visible month. */\n  month?: Date;\n  /** First visible month on mount. Falls back to the selection, then today. */\n  defaultMonth?: Date;\n  onMonthChange?: (month: Date) => void;\n  /** Rendered under the grid (Today button, legend). */\n  footer?: React.ReactNode;\n  /** Accessible name for the calendar group. @default \"Calendar\" */\n  \"aria-label\"?: string;\n  className?: string;\n}\n\nexport interface CalendarSingleProps extends CalendarBaseProps {\n  mode?: \"single\";\n  value?: Date | null;\n  defaultValue?: Date | null;\n  onValueChange?: (date: Date | null) => void;\n}\n\nexport interface CalendarRangeProps extends CalendarBaseProps {\n  mode: \"range\";\n  value?: DateRange;\n  defaultValue?: DateRange;\n  onValueChange?: (range: DateRange) => void;\n}\n\nexport type CalendarProps = CalendarSingleProps | CalendarRangeProps;\n\nconst EMPTY: DateRange = { start: null, end: null };\nconst spring = { type: \"spring\", stiffness: 380, damping: 34 } as const;\n\n/**\n * Month grid for one date or a range. Months slide with a blur in the direction of travel, the range band\n * sweeps from the start day, and the grid follows the APG date-grid keyboard pattern.\n */\nexport function Calendar(props: CalendarProps) {\n  const {\n    today: todayProp,\n    min,\n    max,\n    disabledDates,\n    isDateDisabled,\n    highlightedDates,\n    weekStartsOn = 0,\n    numberOfMonths = 1,\n    month: monthProp,\n    defaultMonth,\n    onMonthChange,\n    footer,\n    className,\n  } = props;\n  const showOutsideDays = props.showOutsideDays ?? numberOfMonths === 1;\n  const isRange = props.mode === \"range\";\n  const reduce = useReducedMotion();\n  const uid = React.useId();\n  const today = useToday(todayProp);\n  const rootRef = React.useRef<HTMLDivElement>(null);\n\n  // ── selection ──\n  const [own, setOwn] = React.useState<DateRange>(() => {\n    if (props.mode === \"range\") return props.defaultValue ?? EMPTY;\n    return { start: props.defaultValue ?? null, end: null };\n  });\n  let selected: DateRange;\n  if (props.mode === \"range\") selected = props.value ?? own;\n  else selected = { start: props.value !== undefined ? props.value : own.start, end: null };\n\n  const commit = (next: DateRange) => {\n    if (props.mode === \"range\") {\n      if (props.value === undefined) setOwn(next);\n      props.onValueChange?.(next);\n    } else {\n      if (props.value === undefined) setOwn(next);\n      props.onValueChange?.(next.start);\n    }\n  };\n\n  // ── visible month ──\n  const anchor = defaultMonth ?? selected.start ?? todayProp ?? null;\n  const [ownMonth, setOwnMonth] = React.useState<Date | null>(anchor ? startOfMonth(anchor) : null);\n  if (ownMonth === null && monthProp === undefined && today) setOwnMonth(startOfMonth(today));\n  const month = monthProp ? startOfMonth(monthProp) : ownMonth;\n  const [dir, setDir] = React.useState(1);\n  const months = month ? Array.from({ length: numberOfMonths }, (_, i) => addMonths(month, i)) : [];\n  const lastMonth = months[months.length - 1];\n\n  const goToMonth = (next: Date) => {\n    const m = startOfMonth(next);\n    if (month && isSameMonth(m, month)) return;\n    setDir(month && compareDays(m, month) < 0 ? -1 : 1);\n    if (monthProp === undefined) setOwnMonth(m);\n    onMonthChange?.(m);\n  };\n\n  // ── disabled / highlighted lookups ──\n  const disabledSet = React.useMemo(() => new Set((disabledDates ?? []).map(toISODate)), [disabledDates]);\n  const highlightSet = React.useMemo(() => new Set((highlightedDates ?? []).map(toISODate)), [highlightedDates]);\n  const isDisabled = (d: Date) =>\n    (!!min && compareDays(d, min) < 0) || (!!max && compareDays(d, max) > 0) || disabledSet.has(toISODate(d)) || !!isDateDisabled?.(d);\n\n  const canPrev = !!month && (!min || compareDays(addDays(month, -1), min) >= 0);\n  const canNext = !!lastMonth && (!max || compareDays(addMonths(lastMonth, 1), max) <= 0);\n\n  // ── roving focus ──\n  const [focusDate, setFocusDate] = React.useState<Date | null>(null);\n  const inView = (d: Date | null | undefined): d is Date => !!d && months.some((m) => isSameMonth(m, d));\n  const activeDate = inView(focusDate) ? focusDate : inView(selected.start) ? selected.start : inView(today) ? today : (months[0] ?? null);\n  const pendingFocus = React.useRef(false);\n  const pageKey = month ? toISODate(month) : \"none\";\n\n  React.useEffect(() => {\n    if (!pendingFocus.current || !activeDate) return;\n    pendingFocus.current = false;\n    rootRef.current?.querySelector<HTMLElement>(`[data-page=\"${pageKey}\"] [data-date=\"${toISODate(activeDate)}\"]`)?.focus();\n  });\n\n  // ── range preview ──\n  const [hover, setHover] = React.useState<Date | null>(null);\n  const picking = isRange && !!selected.start && !selected.end;\n  const previewEnd = picking ? hover : null;\n  let bandStart: Date | null = null;\n  let bandEnd: Date | null = null;\n  if (isRange && selected.start) {\n    const other = selected.end ?? previewEnd ?? selected.start;\n    [bandStart, bandEnd] = compareDays(selected.start, other) <= 0 ? [selected.start, other] : [other, selected.start];\n  }\n\n  const select = (d: Date) => {\n    if (isDisabled(d)) return;\n    setFocusDate(d);\n    if (!isRange) {\n      commit({ start: d, end: null });\n      return;\n    }\n    if (!selected.start || selected.end) commit({ start: d, end: null });\n    else commit(compareDays(d, selected.start) < 0 ? { start: d, end: selected.start } : { start: selected.start, end: d });\n    setHover(null);\n  };\n\n  const moveFocus = (next: Date) => {\n    if (min && compareDays(next, min) < 0) next = startOfDay(min);\n    if (max && compareDays(next, max) > 0) next = startOfDay(max);\n    if (month && lastMonth) {\n      if (compareDays(next, month) < 0) goToMonth(startOfMonth(next));\n      else if (compareDays(next, addMonths(lastMonth, 1)) >= 0) goToMonth(addMonths(startOfMonth(next), -(numberOfMonths - 1)));\n    }\n    setFocusDate(next);\n    if (picking) setHover(next);\n    pendingFocus.current = true;\n  };\n\n  const onGridKeyDown = (e: React.KeyboardEvent) => {\n    if (!activeDate) return;\n    const d = activeDate;\n    const dow = (d.getDay() - weekStartsOn + 7) % 7;\n    const map: Record<string, () => Date> = {\n      ArrowLeft: () => addDays(d, -1),\n      ArrowRight: () => addDays(d, 1),\n      ArrowUp: () => addDays(d, -7),\n      ArrowDown: () => addDays(d, 7),\n      Home: () => addDays(d, -dow),\n      End: () => addDays(d, 6 - dow),\n      PageUp: () => addMonths(d, e.shiftKey ? -12 : -1),\n      PageDown: () => addMonths(d, e.shiftKey ? 12 : 1),\n    };\n    if (map[e.key]) {\n      e.preventDefault();\n      moveFocus(map[e.key]());\n    } else if (e.key === \"Enter\" || e.key === \" \") {\n      e.preventDefault();\n      select(d);\n    }\n  };\n\n  const weekdays = Array.from({ length: 7 }, (_, i) => (weekStartsOn + i) % 7);\n\n  const variants: Variants = {\n    enter: (d: number) => (reduce ? { opacity: 0 } : { opacity: 0, x: d * 28, filter: \"blur(6px)\" }),\n    center: { opacity: 1, x: 0, filter: \"blur(0px)\" },\n    exit: (d: number) => (reduce ? { opacity: 0 } : { opacity: 0, x: d * -28, filter: \"blur(6px)\" }),\n  };\n\n  const navBtn =\n    \"grid size-8 place-items-center rounded-full text-ink-muted transition-colors duration-150 hover:bg-surface-hover hover:text-ink disabled:pointer-events-none disabled:opacity-35 outline-none focus-visible:shadow-[var(--focus-ring)]\";\n\n  return (\n    <div\n      ref={rootRef}\n      role=\"group\"\n      aria-label={props[\"aria-label\"] ?? \"Calendar\"}\n      className={cn(\"relative inline-grid w-max max-w-full gap-3 text-ink\", className)}\n    >\n      <p className=\"sr-only\" aria-live=\"polite\">\n        {months.map((m) => `${MONTH_NAMES[m.getMonth()]} ${m.getFullYear()}`).join(\" and \")}\n      </p>\n      <div className=\"pointer-events-none absolute inset-x-0 top-0 z-10 flex h-8 items-center justify-between\">\n        <button\n          type=\"button\"\n          aria-label=\"Previous month\"\n          disabled={!canPrev}\n          onClick={() => month && goToMonth(addMonths(month, -1))}\n          className={cn(navBtn, \"pointer-events-auto\", numberOfMonths === 1 && \"ml-auto\")}\n        >\n          <ChevronLeft aria-hidden className=\"size-4\" />\n        </button>\n        <button\n          type=\"button\"\n          aria-label=\"Next month\"\n          disabled={!canNext}\n          onClick={() => month && goToMonth(addMonths(month, 1))}\n          className={cn(navBtn, \"pointer-events-auto\")}\n        >\n          <ChevronRight aria-hidden className=\"size-4\" />\n        </button>\n      </div>\n\n      <div className=\"relative overflow-hidden\">\n        <AnimatePresence mode=\"popLayout\" initial={false} custom={dir}>\n          <motion.div\n            key={pageKey}\n            data-page={pageKey}\n            custom={dir}\n            variants={variants}\n            initial=\"enter\"\n            animate=\"center\"\n            exit=\"exit\"\n            transition={reduce ? { duration: 0.15 } : spring}\n            className=\"flex flex-col gap-6 sm:flex-row\"\n          >\n            {month === null && <div aria-hidden className=\"h-[296px] w-[280px]\" />}\n            {months.map((m, mi) => {\n              const titleId = `${uid}-m${mi}`;\n              return (\n                <div key={toISODate(m)} className=\"grid w-[280px] gap-2\">\n                  <h2\n                    id={titleId}\n                    className={cn(\n                      \"flex h-8 items-center font-display text-sm font-semibold tracking-[-0.1px]\",\n                      numberOfMonths === 1 ? \"pl-2\" : \"justify-center\",\n                    )}\n                  >\n                    {MONTH_NAMES[m.getMonth()]} {m.getFullYear()}\n                  </h2>\n                  <table\n                    role=\"grid\"\n                    aria-labelledby={titleId}\n                    className=\"w-full border-collapse\"\n                    onKeyDown={onGridKeyDown}\n                    onPointerLeave={() => setHover(null)}\n                  >\n                    <thead>\n                      <tr>\n                        {weekdays.map((w) => (\n                          <th\n                            key={w}\n                            scope=\"col\"\n                            abbr={WEEKDAY_NAMES[w]}\n                            className=\"h-8 p-0 text-center text-[11px] font-medium uppercase tracking-[0.4px] text-ink-subtle\"\n                          >\n                            {WEEKDAY_NAMES[w].slice(0, 2)}\n                          </th>\n                        ))}\n                      </tr>\n                    </thead>\n                    <tbody>\n                      {monthMatrix(m, weekStartsOn).map((week, wi) => (\n                        <tr key={wi}>\n                          {week.map((d, di) => {\n                            const outside = !isSameMonth(d, m);\n                            if (outside && !showOutsideDays) return <td key={di} role=\"gridcell\" aria-hidden className=\"h-9 p-0\" />;\n                            const disabled = isDisabled(d);\n                            const isToday = isSameDay(d, today);\n                            const isStart = isSameDay(d, selected.start);\n                            const isEnd = isRange && isSameDay(d, selected.end ?? previewEnd);\n                            const endpoint = isStart || (isRange && isEnd);\n                            const inBand = !!bandStart && !!bandEnd && compareDays(d, bandStart) >= 0 && compareDays(d, bandEnd) <= 0;\n                            const bandFirst = inBand && isSameDay(d, bandStart);\n                            const bandLast = inBand && isSameDay(d, bandEnd);\n                            const single = bandFirst && bandLast;\n                            const isActive = !outside && isSameDay(d, activeDate);\n                            const selectedCell = isRange ? inBand && !!selected.end : isStart;\n                            const delay =\n                              previewEnd || reduce || !bandStart ? 0 : Math.min(Math.max(daysBetween(bandStart, d), 0) * 0.014, 0.28);\n                            return (\n                              <td\n                                key={di}\n                                role=\"gridcell\"\n                                data-date={outside ? undefined : toISODate(d)}\n                                tabIndex={outside ? undefined : isActive ? 0 : -1}\n                                aria-selected={selectedCell || (isRange && endpoint) || undefined}\n                                aria-disabled={disabled || undefined}\n                                aria-current={isToday ? \"date\" : undefined}\n                                onClick={() => (outside ? !disabled && goToMonth(d) : select(d))}\n                                onPointerEnter={() => picking && setHover(d)}\n                                onFocus={() => !outside && setFocusDate(d)}\n                                className={cn(\n                                  \"group relative h-9 p-0 text-center outline-none focus-visible:shadow-none\",\n                                  disabled ? \"cursor-not-allowed\" : \"cursor-pointer\",\n                                )}\n                              >\n                                <AnimatePresence>\n                                  {inBand && !single && (\n                                    <motion.span\n                                      aria-hidden\n                                      initial={{ scaleX: 0, opacity: 0 }}\n                                      animate={{ scaleX: 1, opacity: 1 }}\n                                      exit={{ opacity: 0, transition: { duration: 0.12 } }}\n                                      transition={{ ...spring, delay }}\n                                      style={{ originX: bandFirst ? 1 : 0 }}\n                                      className={cn(\n                                        \"absolute inset-y-0.5 bg-volt-soft\",\n                                        bandFirst ? \"left-1/2 right-0\" : bandLast ? \"left-0 right-1/2\" : \"inset-x-0\",\n                                        !bandFirst && (di === 0 || (!showOutsideDays && d.getDate() === 1)) && \"rounded-l-full\",\n                                        !bandLast && (di === 6 || (!showOutsideDays && d.getDate() === daysInMonth(d.getFullYear(), d.getMonth()))) && \"rounded-r-full\",\n                                        previewEnd && \"opacity-70\",\n                                      )}\n                                    />\n                                  )}\n                                </AnimatePresence>\n                                <motion.span\n                                  whileTap={disabled || reduce ? undefined : { scale: 0.88 }}\n                                  transition={{ type: \"spring\", stiffness: 520, damping: 28 }}\n                                  className={cn(\n                                    \"relative z-[1] mx-auto grid size-9 place-items-center rounded-full text-[13px] tabular-nums transition-colors duration-150\",\n                                    \"group-focus-visible:shadow-[var(--focus-ring)]\",\n                                    endpoint\n                                      ? \"font-semibold text-on-volt\"\n                                      : outside\n                                        ? disabled\n                                          ? \"text-ink-subtle/45\"\n                                          : \"text-ink-subtle hover:bg-surface-hover\"\n                                        : inBand\n                                          ? \"text-ink hover:bg-surface-hover\"\n                                          : disabled\n                                            ? \"text-ink-subtle/45\"\n                                            : isToday\n                                              ? \"font-semibold text-volt-ink hover:bg-surface-hover\"\n                                              : \"text-ink hover:bg-surface-hover\",\n                                  )}\n                                >\n                                  {endpoint && (\n                                    <motion.span\n                                      aria-hidden\n                                      layoutId={reduce ? undefined : `${uid}-${isStart ? \"start\" : \"end\"}`}\n                                      transition={spring}\n                                      className={cn(\"absolute inset-0 rounded-full bg-volt\", previewEnd && !isStart && \"opacity-70\")}\n                                    />\n                                  )}\n                                  <span aria-hidden className=\"relative\">\n                                    {d.getDate()}\n                                  </span>\n                                  {(isToday || highlightSet.has(toISODate(d))) && (\n                                    <span\n                                      aria-hidden\n                                      className={cn(\n                                        \"absolute bottom-1 left-1/2 size-1 -translate-x-1/2 rounded-full\",\n                                        endpoint ? \"bg-on-volt\" : isToday ? \"bg-volt-ink\" : \"bg-ember-ink\",\n                                      )}\n                                    />\n                                  )}\n                                </motion.span>\n                                <span className=\"sr-only\">\n                                  {formatDate(d, \"long\")}\n                                  {highlightSet.has(toISODate(d)) ? \", has events\" : \"\"}\n                                </span>\n                              </td>\n                            );\n                          })}\n                        </tr>\n                      ))}\n                    </tbody>\n                  </table>\n                </div>\n              );\n            })}\n          </motion.div>\n        </AnimatePresence>\n      </div>\n      {footer}\n    </div>\n  );\n}\n"},{"path":"LICENSE-geturui.md","type":"registry:file","target":"LICENSE-geturui.md","content":"MIT License\n\nCopyright (c) 2026 geturui.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"}],"categories":["components","calendar","date","range","date grid","scheduling"],"meta":{"tier":"free","license":"MIT","docs":"https://geturui.com/docs/calendar"}}