Search for a command to run...
A date field component that allows users to enter and edit dates.
npx shadcn@latest add @zaidan/calendarpnpx shadcn add @zaidan/calendaryarn dlx shadcn@latest add @zaidan/calendarbunx shadcn@latest add @zaidan/calendarInstall the following dependencies:
npm i @corvu/calendar date-fns lucide-solidpnpm add @corvu/calendar date-fns lucide-solidyarn add @corvu/calendar date-fns lucide-solidbun add @corvu/calendar date-fns lucide-solidCopy and paste the following code into your project.
1import Calendar from "@corvu/calendar";2import { getWeek } from "date-fns";3import { ChevronLeft, ChevronRight } from "lucide-solid";4import { type ComponentProps, Index, type JSX, mergeProps, Show, splitProps } from "solid-js";5import { cn } from "~/lib/utils";6import { Button, buttonVariants } from "~/components/ui/button";7import {8 Select,9 SelectContent,10 SelectItem,11 SelectTrigger,12 SelectValue,13} from "~/components/ui/select";14
15type CalendarSingleValue = Date | null;16type CalendarMultipleValue = Date[];17type CalendarRangeValue = { from: Date | null; to: Date | null };18
19/**20 * Props passed to the customCell render function21 */22type CustomCellProps = {23 /** The date of the cell */24 date: Date;25 /** Whether the date is outside the current month */26 isOutsideMonth: boolean;27 /** Whether the date is selected */28 isSelected: boolean;29 /** Whether the date is disabled */30 isDisabled: boolean;31 /** Whether the date is today */32 isToday: boolean;33};34
35type CalendarBaseProps = Omit<ComponentProps<"div">, "onChange"> & {36 /**37 * Whether to show days from the previous/next months38 * @default true39 */40 showOutsideDays?: boolean;41 /**42 * Whether to always show 6 weeks in the calendar43 * @default false44 */45 fixedWeeks?: boolean;46 /**47 * Number of months to display48 * @default 149 */50 numberOfMonths?: number;51 /**52 * Function to disable specific dates (grayed out, not selectable)53 */54 disabled?: (date: Date) => boolean;55 /**56 * Function to mark specific dates as booked (strikethrough styling)57 * Booked dates are also disabled but have different visual styling58 */59 booked?: (date: Date) => boolean;60 /**61 * The controlled month to display62 */63 month?: Date;64 /**65 * Callback when the displayed month changes66 */67 onMonthChange?: (month: Date) => void;68 /**69 * The initial month to display (uncontrolled)70 */71 defaultMonth?: Date;72 /**73 * Which day of the week to start on (0 = Sunday, 1 = Monday, etc.)74 * @default 175 */76 weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;77 /**78 * Whether to show week numbers in the calendar79 * @default false80 */81 weekNumbers?: boolean;82 /**83 * Custom render function for day cells84 * Allows adding metadata like prices, events, etc.85 */86 customCell?: (props: CustomCellProps) => JSX.Element;87 /**88 * Enable month/year selection via dropdown selects89 * @default false90 */91 monthYearSelection?: boolean;92 /**93 * Start year for the year dropdown (only used when monthYearSelection is true)94 * @default current year - 10095 */96 startYear?: number;97 /**98 * End year for the year dropdown (only used when monthYearSelection is true)99 * @default current year + 10100 */101 endYear?: number;102};103
104type CalendarSingleProps = CalendarBaseProps & {105 mode?: "single";106 value?: CalendarSingleValue;107 onValueChange?: (value: CalendarSingleValue) => void;108 defaultValue?: CalendarSingleValue;109};110
111type CalendarMultipleProps = CalendarBaseProps & {112 mode: "multiple";113 value?: CalendarMultipleValue;114 onValueChange?: (value: CalendarMultipleValue) => void;115 defaultValue?: CalendarMultipleValue;116};117
118type CalendarRangeProps = CalendarBaseProps & {119 mode: "range";120 value?: CalendarRangeValue;121 onValueChange?: (value: CalendarRangeValue) => void;122 defaultValue?: CalendarRangeValue;123};124
125type CalendarProps = CalendarSingleProps | CalendarMultipleProps | CalendarRangeProps;126
127const MONTHS = [128 { label: "Jan", value: 0 },129 { label: "Feb", value: 1 },130 { label: "Mar", value: 2 },131 { label: "Apr", value: 3 },132 { label: "May", value: 4 },133 { label: "Jun", value: 5 },134 { label: "Jul", value: 6 },135 { label: "Aug", value: 7 },136 { label: "Sep", value: 8 },137 { label: "Oct", value: 9 },138 { label: "Nov", value: 10 },139 { label: "Dec", value: 11 },140];141
142const CalendarComponent = (props: CalendarProps) => {143 const currentYear = new Date().getFullYear();144
145 const mergedProps = mergeProps(146 {147 mode: "single" as const,148 showOutsideDays: true,149 fixedWeeks: false,150 numberOfMonths: 1,151 weekStartsOn: 1 as const,152 weekNumbers: false,153 monthYearSelection: false,154 startYear: currentYear - 100,155 endYear: currentYear + 10,156 },157 props,158 );159
160 const [local, others] = splitProps(mergedProps, [161 "class",162 "mode",163 "value",164 "onValueChange",165 "defaultValue",166 "showOutsideDays",167 "fixedWeeks",168 "numberOfMonths",169 "disabled",170 "booked",171 "month",172 "onMonthChange",173 "defaultMonth",174 "weekStartsOn",175 "weekNumbers",176 "customCell",177 "monthYearSelection",178 "startYear",179 "endYear",180 ]);181
182 const formatMonth = (date: Date) => {183 return date.toLocaleString("default", { month: "long", year: "numeric" });184 };185
186 const formatWeekday = (date: Date) => {187 return date.toLocaleString("default", { weekday: "short" }).slice(0, 2);188 };189
190 // Generate years array for dropdown191 const years = () =>192 Array.from({ length: local.endYear - local.startYear + 1 }, (_, i) => {193 const year = local.startYear + i;194 return { label: year.toString(), value: year };195 });196
197 return (198 // @ts-expect-error - Calendar component is not typed correctly199 <Calendar200 mode={local.mode}201 value={local.value as Date | null}202 onValueChange={local.onValueChange as (value: Date | null) => void}203 initialValue={local.defaultValue as Date | null}204 month={local.month}205 onMonthChange={local.onMonthChange}206 initialMonth={local.defaultMonth}207 numberOfMonths={local.numberOfMonths}208 fixedWeeks={local.fixedWeeks}209 disableOutsideDays={!local.showOutsideDays}210 disabled={(date: Date) => local.disabled?.(date) || local.booked?.(date) || false}211 startOfWeek={local.weekStartsOn}212 >213 {/* @ts-expect-error - Calendar component is not typed correctly */}214 {(calendarProps) => (215 <div216 data-slot="calendar"217 class={cn(218 "group/calendar z-calendar w-fit bg-popover p-3",219 "in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent",220 local.class,221 )}222 {...others}223 >224 <div class="flex flex-col gap-4 md:flex-row">225 <Index each={calendarProps.months}>226 {(monthData, index) => (227 <div data-slot="calendar-month" class="flex w-full flex-col gap-4">228 {/* Navigation and Header */}229 <nav230 data-slot="calendar-header"231 class="flex h-(--cell-size) w-full items-center justify-between gap-1"232 >233 <Calendar.Nav234 action="prev-month"235 as={Button}236 variant="ghost"237 class={cn(238 buttonVariants({ variant: "ghost" }),239 "size-(--cell-size) select-none p-0",240 )}241 >242 <ChevronLeft class="size-4" />243 <span class="sr-only">Previous month</span>244 </Calendar.Nav>245
246 {/* Month/Year Selection or Label */}247 <Show248 when={local.monthYearSelection}249 fallback={250 <h2251 class="flex-1 select-none text-center font-medium text-sm"252 data-slot="calendar-label"253 id={calendarProps.labelIds[index]?.()}254 >255 {formatMonth(monthData().month)}256 </h2>257 }258 >259 <div class="flex flex-1 items-center justify-center gap-2">260 <Select<(typeof MONTHS)[number]>261 options={MONTHS}262 optionValue="value"263 optionTextValue="label"264 value={MONTHS.find((m) => m.value === monthData().month.getMonth())}265 onChange={(selectedMonth) => {266 if (selectedMonth) {267 const newDate = new Date(monthData().month);268 newDate.setMonth(selectedMonth.value);269 calendarProps.setMonth(newDate);270 }271 }}272 itemComponent={(itemProps) => (273 <SelectItem item={itemProps.item}>274 {itemProps.item.rawValue.label}275 </SelectItem>276 )}277 >278 <SelectTrigger size="sm">279 <SelectValue<(typeof MONTHS)[number]>>280 {(state) => state.selectedOption().label}281 </SelectValue>282 </SelectTrigger>283 <SelectContent />284 </Select>285 <Select<{ label: string; value: number }>286 options={years()}287 optionValue="value"288 optionTextValue="label"289 value={years().find((y) => y.value === monthData().month.getFullYear())}290 onChange={(selectedYear) => {291 if (selectedYear) {292 const newDate = new Date(monthData().month);293 newDate.setFullYear(selectedYear.value);294 calendarProps.setMonth(newDate);295 }296 }}297 itemComponent={(itemProps) => (298 <SelectItem item={itemProps.item}>299 {itemProps.item.rawValue.label}300 </SelectItem>301 )}302 >303 <SelectTrigger size="sm">304 <SelectValue<{ label: string; value: number }>>305 {(state) => state.selectedOption().label}306 </SelectValue>307 </SelectTrigger>308 <SelectContent />309 </Select>310 </div>311 </Show>312
313 <Calendar.Nav314 action="next-month"315 as={Button}316 variant="ghost"317 class={cn(318 buttonVariants({ variant: "ghost" }),319 "size-(--cell-size) select-none p-0",320 )}321 >322 <ChevronRight class="size-4" />323 <span class="sr-only">Next month</span>324 </Calendar.Nav>325 </nav>326
327 <Calendar.Table index={index} class="w-full border-collapse">328 <thead data-slot="calendar-weekdays">329 <tr class="flex">330 {/* Week number header */}331 <Show when={local.weekNumbers}>332 <th333 data-slot="calendar-week-number-header"334 class="w-8 flex-none select-none rounded-(--cell-radius) font-normal text-[0.8rem] text-muted-foreground"335 >336 #337 </th>338 </Show>339 <Index each={calendarProps.weekdays}>340 {(weekday) => (341 <Calendar.HeadCell342 data-slot="calendar-weekday"343 class="flex-1 select-none rounded-(--cell-radius) font-normal text-[0.8rem] text-muted-foreground"344 >345 {formatWeekday(weekday())}346 </Calendar.HeadCell>347 )}348 </Index>349 </tr>350 </thead>351 <tbody data-slot="calendar-weeks">352 <Index each={monthData().weeks}>353 {(week) => (354 <tr data-slot="calendar-week" class="mt-2 flex w-full">355 {/* Week number cell */}356 <Show when={local.weekNumbers}>357 <td358 data-slot="calendar-week-number"359 class="flex w-8 flex-none select-none items-center justify-center font-normal text-[0.75rem] text-muted-foreground"360 >361 {getWeekNumber(week())}362 </td>363 </Show>364 <Index each={week()}>365 {(day) => (366 <Show when={day()} fallback={<td class="flex-1 p-0" />}>367 {(d) => (368 <CalendarDay369 day={d()}370 month={monthData().month}371 mode={local.mode}372 value={calendarProps.value}373 disabled={local.disabled}374 booked={local.booked}375 customCell={local.customCell}376 />377 )}378 </Show>379 )}380 </Index>381 </tr>382 )}383 </Index>384 </tbody>385 </Calendar.Table>386 </div>387 )}388 </Index>389 </div>390 </div>391 )}392 </Calendar>393 );394};395
396/**397 * Get the ISO week number for a week (using the first non-null day)398 */399const getWeekNumber = (week: (Date | null)[]): number => {400 const firstDay = week.find((d) => d !== null);401 if (!firstDay) return 0;402 return getWeek(firstDay, { weekStartsOn: 1 });403};404
405type CalendarDayProps = {406 day: Date;407 month: Date;408 mode: "single" | "multiple" | "range";409 value: Date | null | Date[] | { from: Date | null; to: Date | null };410 disabled?: (date: Date) => boolean;411 booked?: (date: Date) => boolean;412 customCell?: (props: CustomCellProps) => JSX.Element;413};414
415const CalendarDay = (props: CalendarDayProps) => {416 const isOutsideMonth = () => props.day.getMonth() !== props.month.getMonth();417 const isToday = () => isSameDay(props.day, new Date());418 const isDisabled = () => props.disabled?.(props.day) ?? false;419 const isBooked = () => props.booked?.(props.day) ?? false;420
421 const isSelected = () => {422 const value = props.value;423 if (value === null || value === undefined) return false;424
425 if (value instanceof Date) {426 return isSameDay(value, props.day);427 }428 if (Array.isArray(value)) {429 return value.some((d) => isSameDay(d, props.day));430 }431 if (typeof value === "object" && "from" in value) {432 const { from, to } = value as CalendarRangeValue;433 if (from && isSameDay(from, props.day)) return true;434 if (to && isSameDay(to, props.day)) return true;435 return false;436 }437 return false;438 };439
440 const isRangeStart = () => {441 const value = props.value;442 if (!value || typeof value !== "object" || !("from" in value)) return false;443 const { from } = value as CalendarRangeValue;444 return from && isSameDay(from, props.day);445 };446
447 const isRangeEnd = () => {448 const value = props.value;449 if (!value || typeof value !== "object" || !("from" in value)) return false;450 const { to } = value as CalendarRangeValue;451 return to && isSameDay(to, props.day);452 };453
454 const isInRange = () => {455 const value = props.value;456 if (!value || typeof value !== "object" || !("from" in value)) return false;457 const { from, to } = value as CalendarRangeValue;458 if (!from || !to) return false;459 return props.day > from && props.day < to;460 };461
462 const isSingleSelected = () => isSelected() && !isRangeStart() && !isRangeEnd() && !isInRange();463
464 return (465 <Calendar.Cell466 data-slot="calendar-day"467 class={cn(468 "group/day relative aspect-square h-full w-full flex-1 select-none rounded-(--cell-radius) p-0 text-center",469 "[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)",470 "[&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)",471 isRangeStart() &&472 "isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted",473 isRangeEnd() &&474 "isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted",475 isInRange() && "rounded-none",476 )}477 data-selected={isSelected() || undefined}478 data-outside={isOutsideMonth() || undefined}479 >480 <Calendar.CellTrigger481 day={props.day}482 month={props.month}483 data-slot="calendar-day-button"484 class={cn(485 buttonVariants({ variant: "ghost", size: "icon" }),486 "relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-0.5 border-0 font-normal leading-none",487 // Focus states488 "group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50",489 // Today styling490 "data-[today=true]:data-[selected=true]:rounded-none data-[today=true]:rounded-(--cell-radius) data-[today=true]:bg-muted data-[today=true]:text-foreground",491 // Single selection492 isSingleSelected() && "bg-primary text-primary-foreground",493 // Range selection494 isRangeStart() &&495 "rounded-(--cell-radius) rounded-l-(--cell-radius) bg-primary text-primary-foreground",496 isRangeEnd() &&497 "rounded-(--cell-radius) rounded-r-(--cell-radius) bg-primary text-primary-foreground",498 isInRange() && "rounded-none bg-muted text-foreground",499 // Outside month500 "data-[outside=true]:text-muted-foreground data-[outside=true]:aria-selected:text-muted-foreground",501 // Disabled (not selectable, grayed out)502 "data-disabled:text-muted-foreground data-disabled:opacity-50",503 // Booked (strikethrough styling)504 isBooked() && "line-through",505 // Custom cell styling - add padding if custom cell is provided506 props.customCell && "h-auto min-h-(--cell-size) py-1",507 )}508 data-outside={isOutsideMonth() || undefined}509 >510 <span>{props.day.getDate()}</span>511 <Show when={props.customCell}>512 {(renderCustomCell) =>513 renderCustomCell()({514 date: props.day,515 isOutsideMonth: isOutsideMonth(),516 isSelected: isSelected(),517 isDisabled: isDisabled(),518 isToday: isToday(),519 })520 }521 </Show>522 </Calendar.CellTrigger>523 </Calendar.Cell>524 );525};526
527// Helper function528const isSameDay = (a: Date, b: Date): boolean => {529 return (530 a.getFullYear() === b.getFullYear() &&531 a.getMonth() === b.getMonth() &&532 a.getDate() === b.getDate()533 );534};535
536export { CalendarComponent as Calendar, type CalendarProps, type CustomCellProps };The Calendar component is built on top of Corvu Calendar. It provides a fully accessible date selection component with keyboard navigation and support for single, multiple, and range selection modes.
The Calendar component accepts the following props:
| Prop | Type | Default | Description |
|---|---|---|---|
mode | "single" | "multiple" | "range" | "single" | The selection mode |
value | Date | null | Date[] | { from: Date | null, to: Date | null } | - | The controlled value |
onValueChange | (value) => void | - | Handler called when value changes |
defaultValue | Same as value | - | The default uncontrolled value |
month | Date | - | The controlled month to display |
onMonthChange | (month: Date) => void | - | Handler called when month changes |
defaultMonth | Date | new Date() | The initial month to display |
showOutsideDays | boolean | true | Whether to show days from adjacent months |
fixedWeeks | boolean | false | Whether to always show 6 weeks |
numberOfMonths | number | 1 | Number of months to display |
disabled | (date: Date) => boolean | - | Function to disable specific dates |
weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 1 | Which day of the week to start on (0 = Sunday) |
weekNumbers | boolean | false | Whether to show ISO week numbers |
customCell | (props: CustomCellProps) => JSX.Element | - | Custom render function for day cells |
monthYearSelection | boolean | false | Enable month/year dropdown selection |
startYear | number | currentYear - 100 | Start year for year dropdown |
endYear | number | currentYear + 10 | End year for year dropdown |
class | string | - | Additional CSS classes |
The customCell render function receives the following props:
| Prop | Type | Description |
|---|---|---|
date | Date | The date of the cell |
isOutsideMonth | boolean | Whether the date is outside the current month |
isSelected | boolean | Whether the date is selected |
isDisabled | boolean | Whether the date is disabled |
isToday | boolean | Whether the date is today |
You can use the <Calendar> component to build a date picker by combining it with <Popover> and <Button> components. See the Date Picker examples below.
You can customize the size of calendar cells using the --cell-size CSS variable. You can also make it responsive by using breakpoint-specific values:
1<Calendar2 mode="single"3 value={date()}4 onValueChange={setDate}5 class="rounded-lg border [--cell-size:--spacing(11)] md:[--cell-size:--spacing(12)]"6/>Display ISO week numbers alongside the calendar using the weekNumbers prop:
1<Calendar2 mode="single"3 value={date()}4 onValueChange={setDate}5 weekNumbers6/>Use the customCell prop to add custom content to each day cell. This is useful for displaying metadata like prices, events, or availability indicators:
1import { Show } from "solid-js";2import { Calendar, type CustomCellProps } from "~/components/ui/calendar";3
4const renderPriceCell = (props: CustomCellProps) => {5 const price = getPriceForDate(props.date);6
7 return (8 <Show when={price !== null && !props.isOutsideMonth}>9 <span class={`text-[0.65rem] ${props.isSelected ? "text-primary-foreground/80" : "text-muted-foreground"}`}>10 ${price}11 </span>12 </Show>13 );14};15
16<Calendar17 mode="single"18 value={date()}19 onValueChange={setDate}20 customCell={renderPriceCell}21 class="[--cell-size:--spacing(11)]"22/>Enable dropdown selection for month and year using the monthYearSelection prop:
1<Calendar2 mode="single"3 value={date()}4 onValueChange={setDate}5 monthYearSelection6 startYear={1990}7 endYear={2030}8/>You can combine the calendar with time inputs to create a date-time picker:
1import { createSignal } from "solid-js";2import { Calendar } from "~/components/ui/calendar";3import { Input } from "~/components/ui/input";4
5const [date, setDate] = createSignal<Date | null>(null);6const [time, setTime] = createSignal("10:30");7
8<div>9 <Calendar mode="single" value={date()} onValueChange={setDate} />10 <Input11 type="time"12 value={time()}13 onInput={(e) => setTime(e.currentTarget.value)}14 />15</div>You can style disabled dates differently using the disabled prop and CSS selectors:
1import { Calendar } from "~/components/ui/calendar";2
3const bookedDates = [4 new Date(2024, 0, 15),5 new Date(2024, 0, 20),6];7
8const isBooked = (day: Date) => {9 return bookedDates.some(booked =>10 booked.toDateString() === day.toDateString()11 );12};13
14<Calendar15 mode="single"16 disabled={isBooked}17 class="**:data-[disabled=true]:line-through **:data-[disabled=true]:opacity-30"18/>Here are the source code of all the examples from the preview page:
import { createSignal } from "solid-js";import { Calendar } from "~/components/ui/calendar";import { Card, CardContent } from "~/components/ui/card";function CalendarSingle() { const [date, setDate] = createSignal<Date | null>( new Date(new Date().getFullYear(), new Date().getMonth(), 12), ); return ( <Example title="Single"> <Card class="mx-auto w-fit p-0"> <CardContent class="p-0"> <Calendar mode="single" monthYearSelection value={date()} onValueChange={setDate} /> </CardContent> </Card> </Example> );}import { Calendar } from "~/components/ui/calendar";import { Card, CardContent } from "~/components/ui/card";function CalendarMultiple() { return ( <Example title="Multiple"> <Card class="mx-auto w-fit p-0"> <CardContent class="p-0"> <Calendar mode="multiple" /> </CardContent> </Card> </Example> );}import { createSignal } from "solid-js";import { Calendar } from "~/components/ui/calendar";import { Card, CardContent } from "~/components/ui/card";function CalendarRange() { const addDays = (date: Date, days: number) => { const result = new Date(date); result.setDate(result.getDate() + days); return result; };
const [dateRange, setDateRange] = createSignal<{ from: Date | null; to: Date | null }>({ from: new Date(new Date().getFullYear(), 0, 12), to: addDays(new Date(new Date().getFullYear(), 0, 12), 30), });
return ( <Example title="Range" containerClass="lg:col-span-full 2xl:col-span-full" class="p-12"> <Card class="mx-auto w-fit p-0"> <CardContent class="p-0"> <Calendar mode="range" defaultMonth={dateRange().from ?? undefined} value={dateRange()} onValueChange={setDateRange} numberOfMonths={2} disabled={(date) => date > new Date() || date < new Date("1900-01-01")} /> </CardContent> </Card> </Example> );}import { createSignal } from "solid-js";import { Calendar } from "~/components/ui/calendar";import { Card, CardContent } from "~/components/ui/card";function CalendarRangeMultipleMonths() { const addDays = (date: Date, days: number) => { const result = new Date(date); result.setDate(result.getDate() + days); return result; };
const [range, setRange] = createSignal<{ from: Date | null; to: Date | null }>({ from: new Date(new Date().getFullYear(), 3, 12), to: addDays(new Date(new Date().getFullYear(), 3, 12), 60), });
return ( <Example title="Range Multiple Months" containerClass="lg:col-span-full 2xl:col-span-full" class="p-12" > <Card class="mx-auto w-fit p-0"> <CardContent class="p-0"> <Calendar mode="range" defaultMonth={range().from ?? undefined} value={range()} onValueChange={setRange} numberOfMonths={3} fixedWeeks /> </CardContent> </Card> </Example> );}import { createSignal } from "solid-js";import { Calendar } from "~/components/ui/calendar";import { Card, CardContent } from "~/components/ui/card";function CalendarWeekNumbers() { const [date, setDate] = createSignal<Date | null>( new Date(new Date().getFullYear(), new Date().getMonth(), 15), );
return ( <Example title="Week Numbers"> <Card class="mx-auto w-fit p-0"> <CardContent class="p-0"> <Calendar mode="single" value={date()} onValueChange={setDate} weekNumbers /> </CardContent> </Card> </Example> );}import { createSignal, Show } from "solid-js";import { Calendar, type CustomCellProps } from "~/components/ui/calendar";import { Card, CardContent } from "~/components/ui/card";function CalendarCustomCell() { const [range, setRange] = createSignal<{ from: Date | null; to: Date | null }>({ from: new Date(new Date().getFullYear(), 0, 8), to: addDays(new Date(new Date().getFullYear(), 0, 8), 10), });
const renderPriceCell = (props: CustomCellProps) => { const isWeekend = () => props.date.getDay() === 0 || props.date.getDay() === 6;
return ( <Show when={!props.isOutsideMonth}> <span class={`text-[0.65rem] ${props.isSelected ? "text-primary-foreground/80" : "text-muted-foreground"}`} > ${isWeekend() ? "100" : "80"} </span> </Show> ); };
return ( <Example title="Custom Cell (Pricing)"> <Card class="mx-auto w-fit p-0"> <CardContent class="p-0"> <Calendar mode="range" value={range()} onValueChange={setRange} customCell={renderPriceCell} class="[--cell-size:--spacing(11)]" /> </CardContent> </Card> </Example> );}import { createSignal } from "solid-js";import { Calendar } from "~/components/ui/calendar";import { Card, CardContent } from "~/components/ui/card";import { Field, FieldLabel } from "~/components/ui/field";import { Input } from "~/components/ui/input";function CalendarWithTime() { const [date, setDate] = createSignal<Date | null>( new Date(new Date().getFullYear(), new Date().getMonth(), 15), ); const [startTime, setStartTime] = createSignal("10:30"); const [endTime, setEndTime] = createSignal("12:30");
return ( <Example title="With Time"> <Card class="mx-auto w-fit max-w-sm" size="sm"> <CardContent> <Calendar mode="single" value={date()} onValueChange={setDate} fixedWeeks class="p-0" /> <div class="mt-4 flex gap-2"> <Field class="flex-1"> <FieldLabel for="start-time">Start Time</FieldLabel> <Input id="start-time" type="time" value={startTime()} onInput={(e) => setStartTime(e.currentTarget.value)} /> </Field> <Field class="flex-1"> <FieldLabel for="end-time">End Time</FieldLabel> <Input id="end-time" type="time" value={endTime()} onInput={(e) => setEndTime(e.currentTarget.value)} /> </Field> </div> </CardContent> </Card> </Example> );}import { createSignal } from "solid-js";import { Calendar } from "~/components/ui/calendar";import { Card, CardContent } from "~/components/ui/card";function CalendarBookedDates() { const [date, setDate] = createSignal<Date | null>(null);
// Dates that are "booked" (shown as disabled with different styling) const bookedDates = [ new Date(new Date().getFullYear(), new Date().getMonth(), 8), new Date(new Date().getFullYear(), new Date().getMonth(), 9), new Date(new Date().getFullYear(), new Date().getMonth(), 10), new Date(new Date().getFullYear(), new Date().getMonth(), 15), new Date(new Date().getFullYear(), new Date().getMonth(), 16), new Date(new Date().getFullYear(), new Date().getMonth(), 20), new Date(new Date().getFullYear(), new Date().getMonth(), 25), ];
const isSameDay = (a: Date, b: Date): boolean => { return ( a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate() ); };
const isBooked = (day: Date) => { return bookedDates.some((bookedDate) => isSameDay(bookedDate, day)); };
return ( <Example title="Booked Dates"> <Card class="mx-auto w-fit p-0"> <CardContent class="p-0"> <Calendar mode="single" value={date()} onValueChange={setDate} booked={isBooked} /> </CardContent> </Card> </Example> );}import { createSignal } from "solid-js";import { Button } from "~/components/ui/button";import { Calendar } from "~/components/ui/calendar";import { Card, CardContent, CardFooter } from "~/components/ui/card";function CalendarWithPresets() { const addDays = (date: Date, days: number) => { const result = new Date(date); result.setDate(result.getDate() + days); return result; };
const [date, setDate] = createSignal<Date | null>(new Date(new Date().getFullYear(), 1, 12)); const [currentMonth, setCurrentMonth] = createSignal<Date>( new Date(new Date().getFullYear(), new Date().getMonth(), 1), );
return ( <Example title="With Presets"> <Card class="mx-auto w-fit max-w-[300px]" size="sm"> <CardContent> <Calendar mode="single" value={date()} onValueChange={setDate} month={currentMonth()} onMonthChange={setCurrentMonth} fixedWeeks class="p-0 [--cell-size:--spacing(9.5)]" /> </CardContent> <CardFooter class="flex flex-wrap gap-2 border-t"> {[ { label: "Today", value: 0 }, { label: "Tomorrow", value: 1 }, { label: "In 3 days", value: 3 }, { label: "In a week", value: 7 }, { label: "In 2 weeks", value: 14 }, ].map((preset) => ( <Button variant="outline" size="sm" class="flex-1" onClick={() => { const newDate = addDays(new Date(), preset.value); setDate(newDate); setCurrentMonth(new Date(newDate.getFullYear(), newDate.getMonth(), 1)); }} > {preset.label} </Button> ))} </CardFooter> </Card> </Example> );}import { CalendarIcon } from "lucide-solid";import { createSignal, Show } from "solid-js";import { Button } from "~/components/ui/button";import { Calendar } from "~/components/ui/calendar";import { Field, FieldLabel } from "~/components/ui/field";import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover";function DatePickerSimple() { const [date, setDate] = createSignal<Date | null>(null);
const formatDate = (date: Date) => { return date.toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric", }); };
return ( <Example title="Date Picker Simple"> <Field class="mx-auto w-72"> <FieldLabel for="date-picker-simple">Date</FieldLabel> <Popover> <PopoverTrigger as={Button} variant="outline" id="date-picker-simple" class="justify-start px-2.5 font-normal" > <CalendarIcon data-icon="inline-start" /> <Show when={date()} fallback={<span>Pick a date</span>} keyed> {(d) => formatDate(d)} </Show> </PopoverTrigger> <PopoverContent class="w-auto p-0"> <Calendar mode="single" value={date()} onValueChange={setDate} /> </PopoverContent> </Popover> </Field> </Example> );}import { CalendarIcon } from "lucide-solid";import { createSignal, Show } from "solid-js";import { Button } from "~/components/ui/button";import { Calendar } from "~/components/ui/calendar";import { Field, FieldLabel } from "~/components/ui/field";import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover";import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "~/components/ui/select";function DatePickerWithDropdowns() { const [date, setDate] = createSignal<Date | null>(null);
const formatDate = (date: Date) => { return date.toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric", }); };
return ( <Example title="Date Picker with Dropdowns"> <Field class="mx-auto w-72"> <FieldLabel for="date-picker-dropdowns">Date</FieldLabel> <Popover> <PopoverTrigger as={Button} variant="outline" id="date-picker-dropdowns" class="justify-start px-2.5 font-normal" > <CalendarIcon data-icon="inline-start" /> <Show when={date()} fallback={<span>Pick a date</span>} keyed> {(d) => formatDate(d)} </Show> </PopoverTrigger> <PopoverContent class="w-auto p-0"> <Calendar mode="single" monthYearSelection value={date()} onValueChange={setDate} /> </PopoverContent> </Popover> </Field> </Example> );}import { CalendarIcon } from "lucide-solid";import { createSignal, Show } from "solid-js";import { Button } from "~/components/ui/button";import { Calendar } from "~/components/ui/calendar";import { Field, FieldLabel } from "~/components/ui/field";import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover";function DatePickerWithRange() { const addDays = (date: Date, days: number) => { const result = new Date(date); result.setDate(result.getDate() + days); return result; };
const [date, setDate] = createSignal<{ from: Date | null; to: Date | null }>({ from: new Date(new Date().getFullYear(), 0, 20), to: addDays(new Date(new Date().getFullYear(), 0, 20), 20), });
const formatDate = (date: Date) => { return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", }); };
return ( <Example title="Date Picker Range"> <Field class="mx-auto w-72"> <FieldLabel for="date-picker-range">Date Picker Range</FieldLabel> <Popover> <PopoverTrigger as={Button} variant="outline" id="date-picker-range" class="justify-start px-2.5 font-normal" > <CalendarIcon data-icon="inline-start" /> <Show when={date().from} fallback={<span>Pick a date</span>} keyed> {(from) => ( <Show when={date().to} fallback={formatDate(from)} keyed> {(to) => ( <> {formatDate(from)} - {formatDate(to)} </> )} </Show> )} </Show> </PopoverTrigger> <PopoverContent class="w-auto p-0"> <Calendar mode="range" defaultMonth={date().from ?? undefined} value={date()} onValueChange={setDate} numberOfMonths={2} /> </PopoverContent> </Popover> </Field> </Example> );}