1import { createSignal } from "solid-js";2
3import { Calendar } from "~/components/ui/calendar";4
5export default function CalendarDemo() {6 const [date, setDate] = createSignal<Date | null>(null);7
8 return <Calendar mode="single" value={date()} onValueChange={setDate} />;9}
npx shadcn@latest add https://solid-ui-neobrutalism.vercel.app/r/calendar.json
yarn shadcn@latest add https://solid-ui-neobrutalism.vercel.app/r/calendar.json
pnpm dlx shadcn@latest add https://solid-ui-neobrutalism.vercel.app/r/calendar.json
bunx --bun shadcn@latest add https://solid-ui-neobrutalism.vercel.app/r/calendar.json
Install the following dependencies
npm install @corvu/calendar
yarn add @corvu/calendar
pnpm add @corvu/calendar
bun add @corvu/calendar
Install the following component dependencies
Copy and paste the following code into your project
1import type {2 CellProps,3 CellTriggerProps,4 DynamicProps,5 HeadCellProps,6 LabelProps,7 NavProps,8 RootMultipleProps,9 RootProps,10 RootRangeProps,11 RootSingleProps,12 TableProps,13} from "@corvu/calendar";14import type { ValidComponent } from "solid-js";15
16import {17 createMemo,18 Index,19 Match,20 mergeProps,21 Show,22 splitProps,23 Switch,24} from "solid-js";25import CalendarPrimitive from "@corvu/calendar";26import ChevronLeftIcon from "lucide-solid/icons/chevron-left";27import ChevronRightIcon from "lucide-solid/icons/chevron-right";28
29import { Button } from "./button";30
31const CalendarRoot = (props: RootProps) => {32 return <CalendarPrimitive data-slot="calendar" {...props} />;33};34
35type CalendarNavProps<T extends ValidComponent = "button"> = DynamicProps<36 T,37 NavProps<T>38>;39
40const CalendarNav = <T extends ValidComponent = "button">(41 props: CalendarNavProps<T>,42) => {43 return <CalendarPrimitive.Nav data-slot="calendar-nav" {...props} />;44};45
46const CalendarLabel = <T extends ValidComponent = "h2">(47 props: DynamicProps<T, LabelProps<T>>,48) => {49 return <CalendarPrimitive.Label data-slot="calendar-label" {...props} />;50};51
52const CalendarCell = <T extends ValidComponent = "td">(53 props: DynamicProps<T, CellProps<T>>,54) => {55 return <CalendarPrimitive.Cell data-slot="calendar-cell" {...props} />;56};57
58const CalendarCellTrigger = <T extends ValidComponent = "button">(59 props: DynamicProps<T, CellTriggerProps<T>>,60) => {61 return (62 <CalendarPrimitive.CellTrigger63 data-slot="calendar-cell-trigger"64 {...props}65 />66 );67};68
69const CalendarHeadCell = <T extends ValidComponent = "th">(70 props: DynamicProps<T, HeadCellProps<T>>,71) => {72 return (73 <CalendarPrimitive.HeadCell data-slot="calendar-head-cell" {...props} />74 );75};76
77const CalendarTable = <T extends ValidComponent = "table">(78 props: DynamicProps<T, TableProps<T>>,79) => {80 return <CalendarPrimitive.Table data-slot="calendar-table" {...props} />;81};82
83interface SingleCalendarProps extends Omit<RootSingleProps, "children"> {84 locales: Intl.LocalesArgument;85}86
87const SingleCalendar = (props: SingleCalendarProps) => {88 const [local, rest] = splitProps(props, ["mode", "locales"]);89 const formatWeekdayLong = createMemo(90 () =>91 new Intl.DateTimeFormat(local.locales, {92 weekday: "long",93 }),94 );95 const formatWeekdayShort = createMemo(96 () =>97 new Intl.DateTimeFormat(local.locales, {98 weekday: "short",99 }),100 );101 const formatMonth = createMemo(102 () =>103 new Intl.DateTimeFormat(local.locales, {104 year: "numeric",105 month: "long",106 }),107 );108 return (109 <CalendarRoot mode="single" {...rest}>110 {(calendarProps) => (111 <div class="my-4 rounded-base border-2 border-border p-3">112 <div class="flex items-center justify-between">113 <CalendarNav114 as={Button}115 action="prev-month"116 aria-label="Go to previous month"117 variant="neutral-no-shadow"118 size="icon"119 class="size-7"120 >121 <ChevronLeftIcon class="size-5" />122 </CalendarNav>123 <CalendarLabel class="text-sm">124 {formatMonth().format(calendarProps.month)}125 </CalendarLabel>126 <CalendarNav127 as={Button}128 action="next-month"129 aria-label="Go to next month"130 variant="neutral-no-shadow"131 size="icon"132 class="size-7"133 >134 <ChevronRightIcon class="size-5" />135 </CalendarNav>136 </div>137 <CalendarTable class="mt-3">138 <thead>139 <tr>140 <Index each={calendarProps.weekdays}>141 {(weekday) => (142 <CalendarHeadCell143 abbr={formatWeekdayLong().format(weekday())}144 class="w-8 pb-1 text-xs font-normal opacity-65"145 >146 {formatWeekdayShort().format(weekday())}147 </CalendarHeadCell>148 )}149 </Index>150 </tr>151 </thead>152 <tbody>153 <Index each={calendarProps.weeks}>154 {(week) => (155 <tr>156 <Index each={week()}>157 {(day) => (158 <CalendarCell class="p-0">159 <CalendarCellTrigger160 day={day()}161 class="size-8 rounded-base text-sm transition-colors duration-100 focus-visible:bg-primary/80 disabled:pointer-events-none disabled:opacity-40 data-selected:border-2 data-selected:border-border data-selected:bg-primary! data-selected:text-primary-foreground! data-today:bg-primary/50 md:hover:bg-primary/80"162 >163 {day().getDate()}164 </CalendarCellTrigger>165 </CalendarCell>166 )}167 </Index>168 </tr>169 )}170 </Index>171 </tbody>172 </CalendarTable>173 </div>174 )}175 </CalendarRoot>176 );177};178
179interface MultipleCalendarProps extends Omit<RootMultipleProps, "children"> {180 locales: Intl.LocalesArgument;181 numberOfMonths: number;182}183
184const MultipleCalendar = (props: MultipleCalendarProps) => {185 const [local, rest] = splitProps(props, ["mode", "locales"]);186 const formatWeekdayLong = createMemo(187 () =>188 new Intl.DateTimeFormat(local.locales, {189 weekday: "long",190 }),191 );192 const formatWeekdayShort = createMemo(193 () =>194 new Intl.DateTimeFormat(local.locales, {195 weekday: "short",196 }),197 );198 const formatMonth = createMemo(199 () =>200 new Intl.DateTimeFormat(local.locales, {201 year: "numeric",202 month: "long",203 }),204 );205 return (206 <CalendarRoot mode="multiple" {...rest}>207 {(calendarProps) => (208 <div class="my-4 rounded-base border-2 border-border p-3">209 <div class="flex items-center justify-between">210 <CalendarNav211 as={Button}212 action="prev-month"213 aria-label="Go to previous month"214 variant="neutral-no-shadow"215 size="icon"216 class="size-7"217 >218 <ChevronLeftIcon class="size-5" />219 </CalendarNav>220 <CalendarLabel class="text-sm">221 {formatMonth().format(calendarProps.month)}222 </CalendarLabel>223 <CalendarNav224 as={Button}225 action="next-month"226 aria-label="Go to next month"227 variant="neutral-no-shadow"228 size="icon"229 class="size-7"230 >231 <ChevronRightIcon class="size-5" />232 </CalendarNav>233 </div>234 <CalendarTable class="mt-3">235 <thead>236 <tr>237 <Index each={calendarProps.weekdays}>238 {(weekday) => (239 <CalendarHeadCell240 abbr={formatWeekdayLong().format(weekday())}241 class="w-8 pb-1 text-xs font-normal opacity-65"242 >243 {formatWeekdayShort().format(weekday())}244 </CalendarHeadCell>245 )}246 </Index>247 </tr>248 </thead>249 <tbody>250 <Index each={calendarProps.weeks}>251 {(week) => (252 <tr>253 <Index each={week()}>254 {(day) => (255 <CalendarCell class="p-0">256 <CalendarCellTrigger257 day={day()}258 class="size-8 rounded-base text-sm transition-colors duration-100 focus-visible:bg-primary/80 disabled:pointer-events-none disabled:opacity-40 data-selected:border-2 data-selected:border-border data-selected:bg-primary! data-selected:text-primary-foreground! data-today:bg-primary/50 md:hover:bg-primary/80"259 >260 {day().getDate()}261 </CalendarCellTrigger>262 </CalendarCell>263 )}264 </Index>265 </tr>266 )}267 </Index>268 </tbody>269 </CalendarTable>270 </div>271 )}272 </CalendarRoot>273 );274};275
276interface RangeCalendarProps277 extends Omit<RootRangeProps, "children" | "numberOfMonths"> {278 locales: Intl.LocalesArgument;279 numberOfMonths: number;280}281
282const RangeCalendar = (props: RangeCalendarProps) => {283 const [local, rest] = splitProps(props, [284 "mode",285 "locales",286 "numberOfMonths",287 ]);288 const formatWeekdayLong = createMemo(289 () =>290 new Intl.DateTimeFormat(props.locales, {291 weekday: "long",292 }),293 );294 const formatWeekdayShort = createMemo(295 () =>296 new Intl.DateTimeFormat(props.locales, {297 weekday: "short",298 }),299 );300 const formatMonth = createMemo(301 () =>302 new Intl.DateTimeFormat(props.locales, {303 year: "numeric",304 month: "long",305 }),306 );307 return (308 <CalendarRoot mode="range" numberOfMonths={local.numberOfMonths} {...rest}>309 {(calendarProps) => (310 <div class="relative my-4 rounded-base border-2 border-border p-3">311 <div class="space-y-4 md:flex md:space-y-0 md:space-x-4">312 <Index each={calendarProps.months}>313 {(month, index) => (314 <div>315 <div class="flex h-7 items-center justify-between">316 <Show when={index === 0} fallback={<div class="w-7" />}>317 <CalendarNav318 as={Button}319 action="prev-month"320 aria-label="Go to previous month"321 variant="neutral-no-shadow"322 size="icon"323 class="size-7"324 >325 <ChevronLeftIcon class="size-5" />326 </CalendarNav>327 </Show>328 <CalendarLabel index={index} class="text-sm">329 {formatMonth().format(month().month)}330 </CalendarLabel>331 <Show332 when={index === local.numberOfMonths - 1}333 fallback={<div class="w-7" />}334 >335 <CalendarNav336 as={Button}337 action="next-month"338 aria-label="Go to next month"339 variant="neutral-no-shadow"340 size="icon"341 class="size-7"342 >343 <ChevronRightIcon class="size-5" />344 </CalendarNav>345 </Show>346 </div>347 <CalendarTable index={index} class="mt-3">348 <thead>349 <tr>350 <Index each={calendarProps.weekdays}>351 {(weekday) => (352 <CalendarHeadCell353 abbr={formatWeekdayLong().format(weekday())}354 class="w-8 flex-1 pb-1 text-xs font-normal opacity-65"355 >356 {formatWeekdayShort().format(weekday())}357 </CalendarHeadCell>358 )}359 </Index>360 </tr>361 </thead>362 <tbody>363 <Index each={month().weeks}>364 {(week) => (365 <tr>366 <Index each={week()}>367 {(day) => (368 <CalendarCell class="p-0 has-data-in-range:bg-primary/40 has-data-in-range:first:rounded-l-base has-data-in-range:last:rounded-r-base has-data-range-end:rounded-r-base has-data-range-start:rounded-l-base has-[[disabled]]:opacity-40">369 <CalendarCellTrigger370 day={day()}371 month={month().month}372 class="inline-flex size-8 items-center justify-center rounded-base text-sm focus-visible:bg-primary/80 disabled:pointer-events-none data-range-end:border-2 data-range-end:border-border data-range-end:bg-primary data-range-start:border-2 data-range-start:border-border data-range-start:bg-primary data-today:bg-primary/50 md:hover:not-data-range-start:not-data-range-end:bg-primary/80"373 >374 {day().getDate()}375 </CalendarCellTrigger>376 </CalendarCell>377 )}378 </Index>379 </tr>380 )}381 </Index>382 </tbody>383 </CalendarTable>384 </div>385 )}386 </Index>387 </div>388 </div>389 )}390 </CalendarRoot>391 );392};393
394interface CalendarSingleProps395 extends Omit<RootSingleProps, "children" | "mode"> {396 locales?: Intl.LocalesArgument;397 mode: "single";398}399
400interface CalendarMultipleProps401 extends Omit<RootMultipleProps, "children" | "mode"> {402 locales?: Intl.LocalesArgument;403 mode?: "multiple";404}405
406interface CalendarRangeProps extends Omit<RootRangeProps, "children" | "mode"> {407 locales?: Intl.LocalesArgument;408 mode: "range";409}410
411type CalendarProps =412 | CalendarSingleProps413 | CalendarMultipleProps414 | CalendarRangeProps;415
416const Calendar = (props: CalendarProps) => {417 const merged = mergeProps(418 {419 locales: "en" as Intl.LocalesArgument,420 numberOfMonths: 2,421 },422 props,423 );424
425 return (426 <Switch>427 <Match when={merged.mode === "single" || merged.mode === undefined}>428 <SingleCalendar {...(merged as SingleCalendarProps)} />429 </Match>430 <Match when={merged.mode === "multiple"}>431 <MultipleCalendar {...(merged as MultipleCalendarProps)} />432 </Match>433 <Match when={merged.mode === "range"}>434 <RangeCalendar {...(merged as RangeCalendarProps)} />435 </Match>436 </Switch>437 );438};439
440export {441 Calendar,442 type CalendarProps,443 SingleCalendar,444 type SingleCalendarProps,445 MultipleCalendar,446 type MultipleCalendarProps,447 RangeCalendar,448 type RangeCalendarProps,449 CalendarRoot,450 CalendarNav,451 CalendarLabel,452 CalendarCell,453 CalendarCellTrigger,454 CalendarHeadCell,455 CalendarTable,456};
Update the import paths to match your project setup.
1import { Calendar } from "~/components/ui/calendar";
1const [date, setDate] = createSignal<Date | null>(null);2
3return <Calendar mode="single" value={date()} onValueChange={setDate} />;
1import { createSignal } from "solid-js";2
3import { Calendar } from "~/components/ui/calendar";4
5export default function CalendarDemo() {6 const [date, setDate] = createSignal<Date | null>(null);7
8 return (9 <Calendar10 locales="ko-KR"11 mode="single"12 value={date()}13 onValueChange={setDate}14 />15 );16}
1import { createSignal } from "solid-js";2
3import { Calendar } from "~/components/ui/calendar";4
5export default function CalendarDemo() {6 const [dates, setDates] = createSignal<Date[]>([]);7
8 return <Calendar mode="multiple" value={dates()} onValueChange={setDates} />;9}
1import { createSignal } from "solid-js";2
3import { Calendar } from "~/components/ui/calendar";4
5export default function CalendarDemo() {6 const [dates, setDates] = createSignal<7 | {8 from: Date | null;9 to: Date | null;10 }11 | undefined12 >();13
14 return <Calendar mode="range" value={dates()} onValueChange={setDates} />;15}