Calendar Inline
Original · freealways-visible inline calendar
byWeb InnoventixReact + Tailwind
calendarinlinecalendars
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/calendar-inline.jsoncalendar-inline.tsx
"use client";
import { useEffect, useMemo, useRef, useState, type KeyboardEvent as ReactKeyboardEvent } from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
type Category = "meeting" | "personal" | "deadline" | "focus" | "social" | "reminder";
type CalEvent = {
id: string;
date: string; // YYYY-MM-DD
time: string; // HH:MM (24h)
title: string;
location: string;
category: Category;
};
const CATEGORY_META: Record<
Category,
{ label: string; dot: string; bar: string; chip: string }
> = {
meeting: {
label: "Meetings",
dot: "bg-indigo-500",
bar: "bg-indigo-500",
chip: "bg-indigo-50 text-indigo-700 dark:bg-indigo-500/15 dark:text-indigo-300",
},
personal: {
label: "Personal",
dot: "bg-emerald-500",
bar: "bg-emerald-500",
chip: "bg-emerald-50 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300",
},
deadline: {
label: "Deadlines",
dot: "bg-rose-500",
bar: "bg-rose-500",
chip: "bg-rose-50 text-rose-700 dark:bg-rose-500/15 dark:text-rose-300",
},
focus: {
label: "Focus",
dot: "bg-violet-500",
bar: "bg-violet-500",
chip: "bg-violet-50 text-violet-700 dark:bg-violet-500/15 dark:text-violet-300",
},
social: {
label: "Social",
dot: "bg-sky-500",
bar: "bg-sky-500",
chip: "bg-sky-50 text-sky-700 dark:bg-sky-500/15 dark:text-sky-300",
},
reminder: {
label: "Reminders",
dot: "bg-amber-500",
bar: "bg-amber-500",
chip: "bg-amber-50 text-amber-700 dark:bg-amber-500/15 dark:text-amber-300",
},
};
const EVENTS: CalEvent[] = [
{ id: "e01", date: "2026-06-29", time: "17:00", title: "Renew passport", location: "City Hall, Window 4", category: "reminder" },
{ id: "e02", date: "2026-07-02", time: "09:30", title: "Design sync with Priya", location: "Zoom · Room 2", category: "meeting" },
{ id: "e03", date: "2026-07-06", time: "14:00", title: "Q3 roadmap review", location: "HQ · Boardroom", category: "meeting" },
{ id: "e04", date: "2026-07-09", time: "11:00", title: "1:1 with Marcus", location: "Café Lumen", category: "meeting" },
{ id: "e05", date: "2026-07-11", time: "08:00", title: "Half marathon", location: "Riverside Park", category: "personal" },
{ id: "e06", date: "2026-07-14", time: "16:30", title: "User research readout", location: "Zoom", category: "meeting" },
{ id: "e07", date: "2026-07-16", time: "08:45", title: "Daily standup", location: "Slack Huddle", category: "meeting" },
{ id: "e08", date: "2026-07-16", time: "13:00", title: "Lunch with Aisha", location: "Nori Kitchen", category: "social" },
{ id: "e09", date: "2026-07-16", time: "15:30", title: "Ship Calendar v2", location: "Deploy window", category: "deadline" },
{ id: "e10", date: "2026-07-16", time: "19:00", title: "Deep work: Q3 deck", location: "Do not disturb", category: "focus" },
{ id: "e11", date: "2026-07-21", time: "10:00", title: "Board meeting", location: "HQ · 12th floor", category: "meeting" },
{ id: "e12", date: "2026-07-23", time: "18:00", title: "Team dinner", location: "Trattoria Vico", category: "social" },
{ id: "e13", date: "2026-07-24", time: "17:00", title: "Invoice due — Acme", location: "Finance portal", category: "deadline" },
{ id: "e14", date: "2026-07-28", time: "09:00", title: "Sprint planning", location: "Zoom", category: "meeting" },
{ id: "e15", date: "2026-07-30", time: "12:30", title: "Coffee with Devon", location: "Blue Bottle", category: "social" },
{ id: "e16", date: "2026-08-03", time: "15:00", title: "Dentist checkup", location: "Bright Smile Clinic", category: "personal" },
];
const TODAY = new Date(2026, 6, 16); // Thu, Jul 16 2026 — demo reference date
const WEEKDAYS: { short: string; long: string }[] = [
{ short: "Sun", long: "Sunday" },
{ short: "Mon", long: "Monday" },
{ short: "Tue", long: "Tuesday" },
{ short: "Wed", long: "Wednesday" },
{ short: "Thu", long: "Thursday" },
{ short: "Fri", long: "Friday" },
{ short: "Sat", long: "Saturday" },
];
function cx(...parts: Array<string | false | null | undefined>): string {
return parts.filter(Boolean).join(" ");
}
function startOfMonth(d: Date): Date {
return new Date(d.getFullYear(), d.getMonth(), 1);
}
function addDays(d: Date, n: number): Date {
return new Date(d.getFullYear(), d.getMonth(), d.getDate() + n);
}
function addMonths(d: Date, n: number): Date {
const target = new Date(d.getFullYear(), d.getMonth() + n, 1);
const last = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate();
target.setDate(Math.min(d.getDate(), last));
return target;
}
function addYears(d: Date, n: number): Date {
return addMonths(d, n * 12);
}
function startOfWeek(d: Date): Date {
return addDays(d, -d.getDay());
}
function endOfWeek(d: Date): Date {
return addDays(d, 6 - d.getDay());
}
function sameDay(a: Date, b: Date): boolean {
return (
a.getFullYear() === b.getFullYear() &&
a.getMonth() === b.getMonth() &&
a.getDate() === b.getDate()
);
}
function sameMonth(a: Date, b: Date): boolean {
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();
}
function toKey(d: Date): string {
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${d.getFullYear()}-${m}-${day}`;
}
function clampToMonth(d: Date, month: Date): Date {
const last = new Date(month.getFullYear(), month.getMonth() + 1, 0).getDate();
return new Date(month.getFullYear(), month.getMonth(), Math.min(d.getDate(), last));
}
function formatTime(t: string): string {
const [hStr, mStr] = t.split(":");
const h = Number(hStr);
const period = h >= 12 ? "PM" : "AM";
const h12 = h % 12 === 0 ? 12 : h % 12;
return `${h12}:${mStr} ${period}`;
}
const monthFmt = new Intl.DateTimeFormat("en-US", { month: "long", year: "numeric" });
const longFmt = new Intl.DateTimeFormat("en-US", {
weekday: "long",
month: "long",
day: "numeric",
year: "numeric",
});
const agendaFmt = new Intl.DateTimeFormat("en-US", {
weekday: "long",
month: "long",
day: "numeric",
});
const styles = `
@keyframes ci_pop {
0% { transform: scale(0.82); }
55% { transform: scale(1.06); }
100% { transform: scale(1); }
}
@keyframes ci_pulse {
0%, 100% { opacity: 0.35; transform: scale(1); }
50% { opacity: 1; transform: scale(1.35); }
}
.ci-selected-pop { animation: ci_pop 0.28s ease-out; }
.ci-live-dot { animation: ci_pulse 2.4s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
.ci-selected-pop, .ci-live-dot { animation: none !important; }
}
`;
function ChevronLeft() {
return (
<svg viewBox="0 0 24 24" fill="none" className="h-4 w-4" aria-hidden="true">
<path d="M15 5l-7 7 7 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
function ChevronRight() {
return (
<svg viewBox="0 0 24 24" fill="none" className="h-4 w-4" aria-hidden="true">
<path d="M9 5l7 7-7 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
function PinIcon() {
return (
<svg viewBox="0 0 24 24" fill="none" className="h-3.5 w-3.5 shrink-0" aria-hidden="true">
<path d="M12 21s7-5.686 7-11a7 7 0 10-14 0c0 5.314 7 11 7 11z" stroke="currentColor" strokeWidth="1.6" strokeLinejoin="round" />
<circle cx="12" cy="10" r="2.4" stroke="currentColor" strokeWidth="1.6" />
</svg>
);
}
function EmptyIcon() {
return (
<svg viewBox="0 0 24 24" fill="none" className="h-8 w-8" aria-hidden="true">
<rect x="3" y="5" width="18" height="16" rx="2.5" stroke="currentColor" strokeWidth="1.5" />
<path d="M3 9h18M8 3v4M16 3v4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path d="M8.5 15.5l3 3M11.5 15.5l-3 3" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
}
export default function CalendarInline() {
const reduce = useReducedMotion();
const [viewMonth, setViewMonth] = useState<Date>(startOfMonth(TODAY));
const [selectedDate, setSelectedDate] = useState<Date>(TODAY);
const [focusedDate, setFocusedDate] = useState<Date>(TODAY);
const gridRef = useRef<HTMLDivElement>(null);
const focusFromKeyboard = useRef<boolean>(false);
const eventsByKey = useMemo(() => {
const map = new Map<string, CalEvent[]>();
for (const ev of EVENTS) {
const list = map.get(ev.date);
if (list) list.push(ev);
else map.set(ev.date, [ev]);
}
for (const list of map.values()) list.sort((a, b) => a.time.localeCompare(b.time));
return map;
}, []);
const weeks = useMemo(() => {
const start = startOfWeek(startOfMonth(viewMonth));
const rows: Date[][] = [];
for (let w = 0; w < 6; w++) {
const row: Date[] = [];
for (let d = 0; d < 7; d++) row.push(addDays(start, w * 7 + d));
rows.push(row);
}
return rows;
}, [viewMonth]);
const selectedEvents = useMemo(
() => eventsByKey.get(toKey(selectedDate)) ?? [],
[eventsByKey, selectedDate],
);
const monthEventCount = useMemo(
() => EVENTS.filter((e) => e.date.slice(0, 7) === toKey(viewMonth).slice(0, 7)).length,
[viewMonth],
);
useEffect(() => {
if (!focusFromKeyboard.current) return;
focusFromKeyboard.current = false;
const btn = gridRef.current?.querySelector<HTMLButtonElement>(
`button[data-daykey="${toKey(focusedDate)}"]`,
);
btn?.focus();
}, [focusedDate, viewMonth]);
function moveFocus(day: Date) {
setFocusedDate(day);
if (!sameMonth(day, viewMonth)) setViewMonth(startOfMonth(day));
}
function handleSelect(day: Date) {
setSelectedDate(day);
setFocusedDate(day);
if (!sameMonth(day, viewMonth)) {
focusFromKeyboard.current = true;
setViewMonth(startOfMonth(day));
}
}
function goMonth(delta: number) {
const nv = addMonths(viewMonth, delta);
setViewMonth(nv);
setFocusedDate(clampToMonth(focusedDate, nv));
}
function goToday() {
setViewMonth(startOfMonth(TODAY));
setSelectedDate(TODAY);
setFocusedDate(TODAY);
}
function onGridKeyDown(e: ReactKeyboardEvent<HTMLDivElement>) {
let next: Date | null = null;
switch (e.key) {
case "ArrowLeft":
next = addDays(focusedDate, -1);
break;
case "ArrowRight":
next = addDays(focusedDate, 1);
break;
case "ArrowUp":
next = addDays(focusedDate, -7);
break;
case "ArrowDown":
next = addDays(focusedDate, 7);
break;
case "Home":
next = startOfWeek(focusedDate);
break;
case "End":
next = endOfWeek(focusedDate);
break;
case "PageUp":
next = e.shiftKey ? addYears(focusedDate, -1) : addMonths(focusedDate, -1);
break;
case "PageDown":
next = e.shiftKey ? addYears(focusedDate, 1) : addMonths(focusedDate, 1);
break;
case "Enter":
case " ":
e.preventDefault();
handleSelect(focusedDate);
return;
default:
return;
}
if (next) {
e.preventDefault();
focusFromKeyboard.current = true;
moveFocus(next);
}
}
const captionId = "ci-month-caption";
const monthKey = toKey(viewMonth).slice(0, 7);
const listMotion = reduce
? { hidden: {}, show: {} }
: { hidden: {}, show: { transition: { staggerChildren: 0.05, delayChildren: 0.03 } } };
const itemMotion = reduce
? { hidden: { opacity: 1 }, show: { opacity: 1 } }
: { hidden: { opacity: 0, y: 10 }, show: { opacity: 1, y: 0 } };
return (
<section className="relative w-full overflow-hidden bg-slate-50 px-4 py-16 text-slate-900 sm:px-6 sm:py-20 dark:bg-slate-950 dark:text-slate-100">
<style>{styles}</style>
{/* atmosphere */}
<div aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden">
<div className="absolute -left-24 -top-32 h-80 w-80 rounded-full bg-indigo-300/40 blur-3xl dark:bg-indigo-600/15" />
<div className="absolute -right-20 top-0 h-72 w-72 rounded-full bg-sky-300/40 blur-3xl dark:bg-violet-600/15" />
</div>
<div className="relative mx-auto max-w-4xl">
<header className="mb-8">
<div className="flex items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-indigo-600 dark:text-indigo-400">
<span className="relative flex h-2 w-2">
<span className="ci-live-dot absolute inline-flex h-full w-full rounded-full bg-indigo-500" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-indigo-500" />
</span>
Schedule
</div>
<h2 className="mt-3 text-2xl font-semibold tracking-tight sm:text-3xl">
Your calendar
</h2>
<p className="mt-1.5 text-sm text-slate-500 dark:text-slate-400">
Today is {longFmt.format(TODAY)}. Pick a day to see what’s on.
</p>
</header>
<div className="grid gap-4 lg:grid-cols-[minmax(0,1fr)_20rem]">
{/* Calendar card */}
<div className="rounded-2xl border border-slate-200 bg-white/80 p-4 shadow-sm backdrop-blur-sm sm:p-6 dark:border-slate-800 dark:bg-slate-900/70">
<div className="mb-5 flex items-center justify-between gap-3">
<div className="min-w-0">
<AnimatePresence mode="wait" initial={false}>
<motion.h3
key={monthKey}
id={captionId}
className="truncate text-lg font-semibold tracking-tight"
initial={reduce ? false : { opacity: 0, y: -6 }}
animate={reduce ? {} : { opacity: 1, y: 0 }}
exit={reduce ? {} : { opacity: 0, y: 6 }}
transition={{ duration: 0.18, ease: "easeOut" }}
>
{monthFmt.format(viewMonth)}
</motion.h3>
</AnimatePresence>
<p className="mt-0.5 text-xs text-slate-500 dark:text-slate-400">
{monthEventCount} {monthEventCount === 1 ? "event" : "events"} this month
</p>
</div>
<div className="flex items-center gap-1.5">
<button
type="button"
onClick={goToday}
className="rounded-lg px-2.5 py-1.5 text-xs font-medium text-slate-600 transition-colors hover:bg-slate-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:text-slate-300 dark:hover:bg-slate-800 dark:focus-visible:ring-offset-slate-900"
>
Today
</button>
<button
type="button"
onClick={() => goMonth(-1)}
aria-label="Previous month"
className="grid h-8 w-8 place-items-center rounded-lg border border-slate-200 text-slate-600 transition-colors hover:bg-slate-100 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-800 dark:hover:text-white dark:focus-visible:ring-offset-slate-900"
>
<ChevronLeft />
</button>
<button
type="button"
onClick={() => goMonth(1)}
aria-label="Next month"
className="grid h-8 w-8 place-items-center rounded-lg border border-slate-200 text-slate-600 transition-colors hover:bg-slate-100 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-800 dark:hover:text-white dark:focus-visible:ring-offset-slate-900"
>
<ChevronRight />
</button>
</div>
</div>
<div
ref={gridRef}
role="grid"
aria-labelledby={captionId}
onKeyDown={onGridKeyDown}
className="select-none"
>
{/* weekday header */}
<div role="row" className="grid grid-cols-7 gap-1">
{WEEKDAYS.map((w) => (
<div
key={w.long}
role="columnheader"
aria-label={w.long}
className="pb-2 text-center text-[0.68rem] font-semibold uppercase tracking-wide text-slate-400 dark:text-slate-500"
>
{w.short}
</div>
))}
</div>
{/* weeks */}
<div role="rowgroup" className="space-y-1">
{weeks.map((week, wi) => (
<div role="row" key={wi} className="grid grid-cols-7 gap-1">
{week.map((day) => {
const key = toKey(day);
const inMonth = sameMonth(day, viewMonth);
const isSelected = sameDay(day, selectedDate);
const isToday = sameDay(day, TODAY);
const isFocusTarget = sameDay(day, focusedDate);
const dayEvents = eventsByKey.get(key) ?? [];
const shownDots = dayEvents.slice(0, 3);
const label =
longFmt.format(day) +
(dayEvents.length
? `, ${dayEvents.length} ${dayEvents.length === 1 ? "event" : "events"}`
: ", no events");
return (
<div
role="gridcell"
aria-selected={isSelected}
key={key}
className="relative"
>
<button
type="button"
data-daykey={key}
tabIndex={isFocusTarget ? 0 : -1}
aria-label={label}
aria-current={isToday ? "date" : undefined}
onClick={() => handleSelect(day)}
className={cx(
"group relative flex aspect-square w-full flex-col items-center justify-center rounded-xl text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-900",
isSelected
? "ci-selected-pop bg-indigo-600 text-white shadow-md shadow-indigo-600/25 dark:bg-indigo-500"
: isToday
? "font-semibold text-indigo-700 ring-1 ring-inset ring-indigo-300 hover:bg-indigo-50 dark:text-indigo-300 dark:ring-indigo-500/40 dark:hover:bg-indigo-500/10"
: inMonth
? "text-slate-700 hover:bg-slate-100 dark:text-slate-200 dark:hover:bg-slate-800"
: "text-slate-300 hover:bg-slate-100 dark:text-slate-600 dark:hover:bg-slate-800/60",
)}
>
<span className="leading-none tabular-nums">{day.getDate()}</span>
{/* event dots */}
<span className="absolute bottom-1.5 flex items-center gap-0.5">
{shownDots.map((ev) => (
<span
key={ev.id}
className={cx(
"h-1.5 w-1.5 rounded-full",
isSelected ? "bg-white/85" : CATEGORY_META[ev.category].dot,
)}
/>
))}
{dayEvents.length > 3 && (
<span
className={cx(
"ml-0.5 text-[0.55rem] font-semibold leading-none",
isSelected ? "text-white/85" : "text-slate-400 dark:text-slate-500",
)}
>
+{dayEvents.length - 3}
</span>
)}
</span>
</button>
</div>
);
})}
</div>
))}
</div>
</div>
{/* legend */}
<div className="mt-5 flex flex-wrap items-center gap-x-4 gap-y-2 border-t border-slate-100 pt-4 dark:border-slate-800">
{(Object.keys(CATEGORY_META) as Category[]).map((cat) => (
<span key={cat} className="flex items-center gap-1.5 text-[0.7rem] text-slate-500 dark:text-slate-400">
<span className={cx("h-2 w-2 rounded-full", CATEGORY_META[cat].dot)} />
{CATEGORY_META[cat].label}
</span>
))}
</div>
</div>
{/* Agenda card */}
<aside
aria-label={`Agenda for ${longFmt.format(selectedDate)}`}
className="flex flex-col rounded-2xl border border-slate-200 bg-white/80 p-5 shadow-sm backdrop-blur-sm dark:border-slate-800 dark:bg-slate-900/70"
>
<div className="mb-4">
<p className="text-xs font-medium uppercase tracking-wide text-indigo-600 dark:text-indigo-400">
{sameDay(selectedDate, TODAY) ? "Today" : "Selected"}
</p>
<h3 className="mt-1 text-base font-semibold tracking-tight">
{agendaFmt.format(selectedDate)}
</h3>
<p className="mt-0.5 text-xs text-slate-500 dark:text-slate-400">
{selectedEvents.length
? `${selectedEvents.length} ${selectedEvents.length === 1 ? "event" : "events"} scheduled`
: "Nothing scheduled"}
</p>
</div>
<div aria-live="polite" className="min-h-0 flex-1">
{selectedEvents.length === 0 ? (
<div className="flex h-full flex-col items-center justify-center rounded-xl border border-dashed border-slate-200 py-10 text-center text-slate-400 dark:border-slate-700 dark:text-slate-500">
<EmptyIcon />
<p className="mt-2 text-sm font-medium text-slate-500 dark:text-slate-400">
A clear day
</p>
<p className="mt-0.5 text-xs">No events on this date.</p>
</div>
) : (
<motion.ul
key={toKey(selectedDate)}
variants={listMotion}
initial="hidden"
animate="show"
className="space-y-2.5"
>
{selectedEvents.map((ev) => {
const meta = CATEGORY_META[ev.category];
return (
<motion.li
key={ev.id}
variants={itemMotion}
className="flex gap-3 rounded-xl border border-slate-200 bg-slate-50/70 p-3 dark:border-slate-800 dark:bg-slate-800/40"
>
<span className={cx("w-1 shrink-0 self-stretch rounded-full", meta.bar)} />
<div className="min-w-0 flex-1">
<div className="flex items-center justify-between gap-2">
<span className="text-xs font-semibold tabular-nums text-slate-500 dark:text-slate-400">
{formatTime(ev.time)}
</span>
<span className={cx("rounded-full px-2 py-0.5 text-[0.62rem] font-medium", meta.chip)}>
{meta.label.replace(/s$/, "")}
</span>
</div>
<p className="mt-1 truncate text-sm font-semibold text-slate-800 dark:text-slate-100">
{ev.title}
</p>
<p className="mt-0.5 flex items-center gap-1 text-xs text-slate-500 dark:text-slate-400">
<PinIcon />
<span className="truncate">{ev.location}</span>
</p>
</div>
</motion.li>
);
})}
</motion.ul>
)}
</div>
<p className="mt-4 border-t border-slate-100 pt-3 text-[0.7rem] leading-relaxed text-slate-400 dark:border-slate-800 dark:text-slate-500">
Tip: use arrow keys to move by day, <kbd className="rounded border border-slate-300 px-1 font-sans dark:border-slate-600">PageUp</kbd>/<kbd className="rounded border border-slate-300 px-1 font-sans dark:border-slate-600">PageDown</kbd> to change month, and <kbd className="rounded border border-slate-300 px-1 font-sans dark:border-slate-600">Enter</kbd> to select.
</p>
</aside>
</div>
</div>
</section>
);
}Dependencies
motion
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Calendar Date Picker
Originaldate picker with a dropdown calendar

Calendar Range Picker
Originaldate range picker with two months

Calendar Month Year
Originalmonth and year quick selector

Calendar Event
Originalmonth calendar with event dots

Calendar Mini
Originalcompact mini calendar widget

Calendar Time Picker
Originaltime picker with hour/minute

Calendar Datetime
Originalcombined date and time picker

Calendar Week
Originalweek view with time slots

Calendar Agenda
Originalagenda list of upcoming days

Calendar Availability
Originalavailability slot picker

Calendar Booking
Originalbooking calendar with time slots

Calendar Multi Month
Originaltwo-month scrolling calendar

