Calendar
calendaruiSingle-month calendar grid for date selection (hand-rolled runes state + @internationalized/date). Use inline or in a custom popover. For form fields, use Date Picker. Supports min/max, disabled dates, multi-month, and locale.
When to use which
Decision GuideCalendar is the always-visible month grid. Date Picker is the form field with popover. Range Calendar manages multi-month spans, and Event Calendar is the multi-view scheduling engine.
| Component | Role | Use when |
|---|---|---|
| Calendarcurrent Embedded dashboard views or scheduling pages where the month grid stays continuously visible. | Grid Primitive | |
| RangeCalendar Contiguous multi-month booking ranges, check-in / check-out spans, and date windows. | Range Primitive | |
| DatePicker Forms, filter bars, and dialogs where space is tight and the calendar opens inside a popover. | Form Control | |
| EventCalendar Full calendar interfaces with month/week/day views, timed hourly intervals, and overlap collision lanes. | Scheduling Engine |
Installation
$pnpm dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/calendar.json$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/calendar.json$yarn dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/calendar.json$bunx shadcn-svelte@latest add https://uipkge.dev/r/svelte/calendar.jsonhttps://uipkge.dev/r/svelte/calendar.jsonInstalls to:src/lib/components/ui/calendar/Variants
Examples in Use
Full BlockComplete scheduling suite composing calendar date navigation, category color-coding, upcoming agenda side-rail, and dynamic modal workflows.
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
valueSelected date (`type="single"`), dates (`type="multiple"`), or range (`type="range"` as `{ start, end }`). Two-way bindable (`bind:value`). INTENTIONAL DIVERGENCE from React: React's DayPicker-based Calendar uses native `Date` (`selected: Date | Date[] | { from, to }`). This Svelte calendar keeps `DateValue` to match the Vue/reka-ui twin. Convert at the boundary with `new CalendarDate(d.getFullYear(), d.getMonth() + 1, d.getDate())`. | DateValue | DateValue[] | CalendarRange | — | optional |
placeholderViewed month. Two-way bindable (`bind:placeholder`). | DateValue | — | optional |
type | 'single' | 'multiple' | 'range' | single | optional |
modeReact DayPicker-parity alias for `type`. When set, overrides `type`. | 'single' | 'multiple' | 'range' | — | optional |
showOutsideDaysWhether to render days outside the current month. Mirrors React `showOutsideDays` (default true). When false, outside days render as invisible placeholders. | boolean | true | optional |
numberOfMonths | number | 1 | optional |
pagedNavigation | boolean | false | optional |
weekStartsOn | number | — | optional |
fixedWeeks | boolean | false | optional |
weekdayFormat | 'narrow' | 'short' | 'long' | narrow | optional |
locale | string | en | optional |
minValue | DateValue | — | optional |
maxValue | DateValue | — | optional |
disabled | boolean | false | optional |
readonly | boolean | false | optional |
isDateDisabled | (date: DateValue) => boolean | — | optional |
isDateUnavailable | (date: DateValue) => boolean | — | optional |
layout | LayoutTypes | — | optional |
yearRange | DateValue[] | — | optional |
onValueChange | (value: DateValue | DateValue[] | CalendarRange | undefined) => void | — | optional |
onPlaceholderChange | (placeholder: DateValue) => void | — | optional |
prevIconOverrides the prev/next chevron icons. | Snippet | — | optional |
nextIcon | Snippet | — | optional |
headingOverrides the whole heading area. Receives the viewed date plus month/year jump helpers. | Snippet< | — | optional |
date | DateValue | — | required |
headingValue | string | — | required |
months | CalendarDate[] | — | required |
years | DateValue[] | — | required |
setMonth | (month: number) => void | — | required |
setYear | (year: number) => void | — | required |
Schema
Type aliases from this item's source — use them to shape the data you pass in.
CalendarRangeinterface CalendarRange {
start: DateValue | undefined
end: DateValue | undefined
}CalendarGridDatainterface CalendarGridData {
/** First day of the rendered month. */
value: CalendarDate
rows: CalendarDate[][]
}CalendarStateOptionsinterface CalendarStateOptions {
onValueChange?: (value: DateValue | DateValue[] | CalendarRange | undefined) => void
onPlaceholderChange?: (placeholder: DateValue) => void
}npm dependencies
Includes
Used by
Files installed (16)
src/lib/components/ui/calendar/Calendar.svelte11.7 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' import { CalendarDate } from '@internationalized/date' import type { DateValue } from '@internationalized/date' import type { CalendarRange } from './calendar-state.svelte' import type { LayoutTypes } from './index' export interface CalendarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'placeholder'> { /** * Selected date (`type="single"`), dates (`type="multiple"`), or range * (`type="range"` as `{ start, end }`). Two-way bindable (`bind:value`). * * INTENTIONAL DIVERGENCE from React: React's DayPicker-based Calendar uses * native `Date` (`selected: Date | Date[] | { from, to }`). This Svelte * calendar keeps `DateValue` to match the Vue/reka-ui twin. Convert at the * boundary with `new CalendarDate(d.getFullYear(), d.getMonth() + 1, d.getDate())`. */ value?: DateValue | DateValue[] | CalendarRange /** Viewed month. Two-way bindable (`bind:placeholder`). */ placeholder?: DateValue type?: 'single' | 'multiple' | 'range' /** React DayPicker-parity alias for `type`. When set, overrides `type`. */ mode?: 'single' | 'multiple' | 'range' /** Whether to render days outside the current month. Mirrors React `showOutsideDays` (default true). When false, outside days render as invisible placeholders. */ showOutsideDays?: boolean numberOfMonths?: number pagedNavigation?: boolean weekStartsOn?: number fixedWeeks?: boolean weekdayFormat?: 'narrow' | 'short' | 'long' locale?: string minValue?: DateValue maxValue?: DateValue disabled?: boolean readonly?: boolean isDateDisabled?: (date: DateValue) => boolean isDateUnavailable?: (date: DateValue) => boolean layout?: LayoutTypes yearRange?: DateValue[] onValueChange?: (value: DateValue | DateValue[] | CalendarRange | undefined) => void onPlaceholderChange?: (placeholder: DateValue) => void /** Overrides the prev/next chevron icons. */ prevIcon?: Snippet nextIcon?: Snippet /** Overrides the whole heading area. Receives the viewed date plus month/year jump helpers. */ heading?: Snippet< [ { date: DateValue headingValue: string months: CalendarDate[] years: DateValue[] setMonth: (month: number) => void setYear: (year: number) => void }, ] > /** Overrides a day cell's content. */ cell?: Snippet<[{ day: CalendarDate; month: CalendarDate }]> ref?: HTMLDivElement | null } </script> <script lang="ts"> import { setContext } from 'svelte' import { getLocalTimeZone, today } from '@internationalized/date' import { ChevronDown } from '@lucide/svelte' import { cn } from '$lib/utils' import { CALENDAR_CONTEXT_KEY, CalendarState } from './calendar-state.svelte' import CalendarCell from './CalendarCell.svelte' import CalendarCellTrigger from './CalendarCellTrigger.svelte' import CalendarGrid from './CalendarGrid.svelte' import CalendarGridBody from './CalendarGridBody.svelte' import CalendarGridHead from './CalendarGridHead.svelte' import CalendarGridRow from './CalendarGridRow.svelte' import CalendarHeadCell from './CalendarHeadCell.svelte' import CalendarHeader from './CalendarHeader.svelte' import CalendarHeading from './CalendarHeading.svelte' import CalendarNextButton from './CalendarNextButton.svelte' import CalendarPrevButton from './CalendarPrevButton.svelte' let { class: className, value = $bindable<DateValue | DateValue[] | CalendarRange | undefined>(undefined), placeholder = $bindable<DateValue | undefined>(undefined), type = 'single', mode = undefined, showOutsideDays = true, numberOfMonths = 1, pagedNavigation = false, weekStartsOn = undefined, fixedWeeks = false, weekdayFormat = 'narrow', locale = 'en', minValue = undefined, maxValue = undefined, disabled = false, readonly = false, isDateDisabled = undefined, isDateUnavailable = undefined, layout = undefined, yearRange: yearRangeProp = undefined, onValueChange, onPlaceholderChange, prevIcon, nextIcon, heading, cell, children, ref = $bindable(null), ...restProps }: CalendarProps = $props() // React parity: `mode` overrides `type` when set (`mode="range"` === `type="range"`). const effectiveType = $derived(mode ?? type) // Initial capture is intentional: props sync into the state via the $effect below. // svelte-ignore state_referenced_locally const state = new CalendarState( { type: effectiveType, locale, weekStartsOn, fixedWeeks, numberOfMonths, pagedNavigation, disabled, readonly, weekdayFormat, minValue, maxValue, isDateDisabled, isDateUnavailable, showOutsideDays, value, placeholder, }, { onValueChange: (v) => { value = v onValueChange?.(v) }, onPlaceholderChange: (p) => { placeholder = p onPlaceholderChange?.(p) }, }, ) setContext(CALENDAR_CONTEXT_KEY, state) // Inward sync: parent props → state (equality-guarded, never loops with // the outward callbacks above). $effect(() => { state.syncFromProps({ type: effectiveType, locale, weekStartsOn, fixedWeeks, numberOfMonths, pagedNavigation, disabled, readonly, weekdayFormat, minValue, maxValue, isDateDisabled, isDateUnavailable, showOutsideDays, value, placeholder, }) }) // The state owns the keyboard-focus root for arrow-key day navigation. $effect(() => { state.rootEl = ref }) const yearRange = $derived.by((): DateValue[] => { if (yearRangeProp) return yearRangeProp const base = state.placeholder ?? today(getLocalTimeZone()) const start = minValue ?? base.subtract({ years: 100 }) const end = maxValue ?? base.add({ years: 10 }) const years: DateValue[] = [] let current = start let guard = 0 while (current.compare(end) <= 0 && guard++ < 500) { years.push(current) current = current.add({ years: 1 }) } return years }) const monthsOfYear = $derived.by((): CalendarDate[] => { const y = state.placeholder.year return Array.from({ length: 12 }, (_, i) => new CalendarDate(y, i + 1, 1)) }) const monthName = $derived.by(() => { const fmt = new Intl.DateTimeFormat(locale, { month: 'short' }) return (d: DateValue) => fmt.format(new Date(d.year, d.month - 1, 1)) }) const yearName = $derived.by(() => { const fmt = new Intl.DateTimeFormat(locale, { year: 'numeric' }) return (d: DateValue) => fmt.format(new Date(d.year, d.month - 1, 1)) }) /** Clamp the day so Jan 31 → Feb lands on Feb 28/29 instead of overflowing. */ function clampDay(year: number, month: number, day: number): number { return Math.min(day, new Date(year, month, 0).getDate()) } function setMonth(month: number) { const p = state.placeholder state.setPlaceholder(new CalendarDate(p.year, month, clampDay(p.year, month, p.day))) } function setYear(year: number) { const p = state.placeholder state.setPlaceholder(new CalendarDate(year, p.month, clampDay(year, p.month, p.day))) } function onMonthChange(e: Event) { setMonth(Number((e.target as HTMLSelectElement | null)?.value)) } function onYearChange(e: Event) { setYear(Number((e.target as HTMLSelectElement | null)?.value)) } </script> {#snippet monthSelect(date: DateValue)} <div class="relative inline-flex items-center"> <select data-uipkge data-slot="calendar-month-select" class="border-input hover:bg-accent/60 text-foreground focus-visible:ring-ring h-8 cursor-pointer appearance-none rounded-md border bg-transparent pr-6 pl-2.5 text-xs font-medium transition-colors focus-visible:ring-1 focus-visible:outline-none" value={date.month} onchange={onMonthChange} > {#each monthsOfYear as month (month.toString())} <option value={month.month} class="bg-popover text-popover-foreground"> {monthName(month)} </option> {/each} </select> <ChevronDown class="text-muted-foreground pointer-events-none absolute right-1.5 size-3.5 opacity-60" /> </div> {/snippet} {#snippet yearSelect(date: DateValue)} <div class="relative inline-flex items-center"> <select data-uipkge data-slot="calendar-year-select" class="border-input hover:bg-accent/60 text-foreground focus-visible:ring-ring h-8 cursor-pointer appearance-none rounded-md border bg-transparent pr-6 pl-2.5 text-xs font-medium transition-colors focus-visible:ring-1 focus-visible:outline-none" value={date.year} onchange={onYearChange} > {#each yearRange as year (year.toString())} <option value={year.year} class="bg-popover text-popover-foreground"> {yearName(year)} </option> {/each} </select> <ChevronDown class="text-muted-foreground pointer-events-none absolute right-1.5 size-3.5 opacity-60" /> </div> {/snippet} <div bind:this={ref} data-uipkge data-slot="calendar" class={cn('p-3', className)} {...restProps}> <CalendarHeader class="pt-0"> <nav class="absolute inset-x-0 top-0 flex items-center justify-between gap-1" aria-label="Calendar navigation"> <CalendarPrevButton icon={prevIcon} /> <CalendarNextButton icon={nextIcon} /> </nav> {#if heading} {@render heading({ date: state.placeholder, headingValue: state.headingValue, months: monthsOfYear, years: yearRange, setMonth, setYear, })} {:else if layout === 'month-and-year'} <div class="flex items-center justify-center gap-1"> {@render monthSelect(state.placeholder)} {@render yearSelect(state.placeholder)} </div> {:else if layout === 'month-only'} <div class="flex items-center justify-center gap-1"> {@render monthSelect(state.placeholder)} {yearName(state.placeholder)} </div> {:else if layout === 'year-only'} <div class="flex items-center justify-center gap-1"> {monthName(state.placeholder)} {@render yearSelect(state.placeholder)} </div> {:else} <CalendarHeading /> {/if} </CalendarHeader> <div class="mt-4 flex flex-col gap-y-4 sm:flex-row sm:gap-x-4 sm:gap-y-0"> {#each state.grids as month (month.value.toString())} <CalendarGrid class="motion-safe:animate-[calendar-month-in_220ms_cubic-bezier(0.22,1,0.36,1)_both]"> <CalendarGridHead> <CalendarGridRow> {#each state.weekDays as day, i (i)} <CalendarHeadCell> {day} </CalendarHeadCell> {/each} </CalendarGridRow> </CalendarGridHead> <CalendarGridBody> {#each month.rows as weekDates, index (`weekDate-${index}`)} <CalendarGridRow class="mt-2 w-full"> {#each weekDates as weekDate (weekDate.toString())} <CalendarCell date={weekDate}> <CalendarCellTrigger day={weekDate} month={month.value} {cell} /> </CalendarCell> {/each} </CalendarGridRow> {/each} </CalendarGridBody> </CalendarGrid> {/each} </div> {@render children?.()} </div> <style> :global { @keyframes calendar-month-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } } @media (prefers-reduced-motion: reduce) { :global([data-slot='calendar'] [class*='animate-\[calendar-month']) { animation: none !important; } } </style>src/lib/components/ui/calendar/CalendarCell.svelte1.4 kB
<script lang="ts" module> import type { HTMLAttributes } from 'svelte/elements' import type { CalendarDate } from '@internationalized/date' export interface CalendarCellProps extends HTMLAttributes<HTMLTableCellElement> { date: CalendarDate ref?: HTMLTableCellElement | null } </script> <script lang="ts"> import { getContext } from 'svelte' import { cn } from '$lib/utils' import { CALENDAR_CONTEXT_KEY, type CalendarState } from './calendar-state.svelte' let { class: className, date, children, ref = $bindable(null), ...restProps }: CalendarCellProps = $props() const state = getContext<CalendarState | undefined>(CALENDAR_CONTEXT_KEY) const disabledDate = $derived(state?.isDisabledDate(date) ?? false) const isRange = $derived(state?.type === 'range') </script> <td bind:this={ref} data-uipkge data-slot="calendar-cell" data-disabled={disabledDate ? '' : undefined} class={cn( 'relative flex-1 p-0 text-center text-sm focus-within:relative focus-within:z-20', isRange && '[&:has([data-selected])]:bg-accent [&:has([data-highlighted])]:bg-accent first:[&:has([data-selected])]:rounded-l-md last:[&:has([data-selected])]:rounded-r-md [&:has([data-selected][data-selection-end])]:rounded-r-md [&:has([data-selected][data-selection-start])]:rounded-l-md', className, )} {...restProps} > {@render children?.()} </td>src/lib/components/ui/calendar/CalendarCellTrigger.svelte5.3 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLButtonAttributes } from 'svelte/elements' import type { CalendarDate } from '@internationalized/date' export interface CalendarCellTriggerProps extends HTMLButtonAttributes { day: CalendarDate month: CalendarDate /** Overrides the day content (defaults to the day number). Receives the same `{ day, month }`. */ cell?: Snippet<[{ day: CalendarDate; month: CalendarDate }]> children?: Snippet ref?: HTMLButtonElement | null } </script> <script lang="ts"> import { getContext } from 'svelte' import { cn } from '$lib/utils' import { buttonVariants } from '$lib/components/ui/button/button.variants' import { CALENDAR_CONTEXT_KEY, type CalendarState } from './calendar-state.svelte' let { class: className, day, month, cell, children, ref = $bindable(null), ...restProps }: CalendarCellTriggerProps = $props() const state = getContext<CalendarState | undefined>(CALENDAR_CONTEXT_KEY) const selected = $derived(state?.isSelected(day) ?? false) const selectionStart = $derived(state?.isSelectionStart(day) ?? false) const selectionEnd = $derived(state?.isSelectionEnd(day) ?? false) const highlighted = $derived(state?.isHighlighted(day) ?? false) const disabledDate = $derived(state?.isDisabledDate(day) ?? false) const unavailable = $derived(state?.isUnavailableDate(day) ?? false) const isToday = $derived(state?.isTodayDate(day) ?? false) const outsideView = $derived(state ? state.isOutsideView(day, month) : false) const isRange = $derived(state?.type === 'range') const hiddenOutside = $derived(outsideView && state?.showOutsideDays === false) </script> <button bind:this={ref} type="button" data-uipkge data-slot="calendar-cell-trigger" data-day={day.toString()} data-value={day.toString()} data-selected={selected ? '' : undefined} data-selection-start={selectionStart || undefined} data-selection-end={selectionEnd || undefined} data-highlighted={highlighted || undefined} data-disabled={disabledDate ? '' : undefined} data-unavailable={unavailable ? '' : undefined} data-today={isToday ? '' : undefined} data-outside-view={outsideView ? '' : undefined} aria-label={state?.formatDay(day)} aria-hidden={hiddenOutside || undefined} aria-disabled={unavailable && !disabledDate ? 'true' : undefined} disabled={disabledDate || hiddenOutside} tabindex={hiddenOutside || (state && !state.isTabbableDate(day)) ? -1 : 0} class={cn( buttonVariants({ variant: 'ghost' }), 'size-9 cursor-pointer p-0 font-normal transition-[color,background-color,transform,box-shadow] duration-150 aria-selected:opacity-100', '[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground', // Single / multiple: selected day gets the primary fill. !isRange && 'data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[selected]:hover:bg-primary data-[selected]:hover:text-primary-foreground data-[selected]:focus:bg-primary data-[selected]:focus:text-primary-foreground data-[selected]:opacity-100 data-[selected]:shadow-sm motion-safe:data-[selected]:animate-[calendar-day-pop_220ms_cubic-bezier(0.22,1.4,0.36,1)_both]', // Range: middle days get the accent fill, start/end get primary + pop (mirrors RangeCalendar). isRange && 'data-[selected]:bg-accent data-[selected]:text-accent-foreground data-[selected]:opacity-100', isRange && 'data-[selection-start]:bg-primary data-[selection-start]:text-primary-foreground data-[selection-start]:hover:bg-primary data-[selection-start]:hover:text-primary-foreground data-[selection-start]:focus:bg-primary data-[selection-start]:focus:text-primary-foreground motion-safe:data-[selection-start]:animate-[calendar-day-pop_220ms_cubic-bezier(0.22,1.4,0.36,1)_both]', isRange && 'data-[selection-end]:bg-primary data-[selection-end]:text-primary-foreground data-[selection-end]:hover:bg-primary data-[selection-end]:hover:text-primary-foreground data-[selection-end]:focus:bg-primary data-[selection-end]:focus:text-primary-foreground motion-safe:data-[selection-end]:animate-[calendar-day-pop_220ms_cubic-bezier(0.22,1.4,0.36,1)_both]', // Disabled 'data-[disabled]:text-muted-foreground data-[disabled]:opacity-50', // Unavailable 'data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through', // Outside months 'data-[outside-view]:text-muted-foreground', hiddenOutside && 'invisible pointer-events-none', className, )} onclick={() => state?.select(day)} onkeydown={(e) => state?.handleDayKeydown(e, day)} onmouseenter={() => state?.hover(day)} onmouseleave={() => state?.hover(null)} onfocus={() => { if (state) { state.focusedDate = day state.hover(null) } }} {...restProps} > {#if cell} {@render cell({ day, month })} {:else if children} {@render children()} {:else} {day.day} {/if} </button> <style> :global { @keyframes calendar-day-pop { 0% { transform: scale(0.86); } 70% { transform: scale(1.06); } 100% { transform: scale(1); } } } @media (prefers-reduced-motion: reduce) { :global([data-slot='calendar-cell-trigger']) { animation: none !important; transition: none !important; } } </style>src/lib/components/ui/calendar/CalendarGrid.svelte0.5 kB
<script lang="ts" module> import type { HTMLAttributes } from 'svelte/elements' export interface CalendarGridProps extends HTMLAttributes<HTMLTableElement> { ref?: HTMLTableElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, children, ref = $bindable(null), ...restProps }: CalendarGridProps = $props() </script> <table bind:this={ref} data-uipkge data-slot="calendar-grid" class={cn('w-full border-collapse', className)} {...restProps}> {@render children?.()} </table>src/lib/components/ui/calendar/CalendarGridBody.svelte0.5 kB
<script lang="ts" module> import type { HTMLAttributes } from 'svelte/elements' export interface CalendarGridBodyProps extends HTMLAttributes<HTMLTableSectionElement> { ref?: HTMLTableSectionElement | null } </script> <script lang="ts"> let { children, ref = $bindable(null), ...restProps }: CalendarGridBodyProps = $props() </script> <tbody bind:this={ref} data-uipkge data-slot="calendar-grid-body" {...restProps}> {@render children?.()} </tbody>src/lib/components/ui/calendar/CalendarGridHead.svelte0.5 kB
<script lang="ts" module> import type { HTMLAttributes } from 'svelte/elements' export interface CalendarGridHeadProps extends HTMLAttributes<HTMLTableSectionElement> { ref?: HTMLTableSectionElement | null } </script> <script lang="ts"> let { class: className, children, ref = $bindable(null), ...restProps }: CalendarGridHeadProps = $props() </script> <thead bind:this={ref} data-uipkge data-slot="calendar-grid-head" class={className} {...restProps}> {@render children?.()} </thead>src/lib/components/ui/calendar/CalendarGridRow.svelte0.5 kB
<script lang="ts" module> import type { HTMLAttributes } from 'svelte/elements' export interface CalendarGridRowProps extends HTMLAttributes<HTMLTableRowElement> { ref?: HTMLTableRowElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, children, ref = $bindable(null), ...restProps }: CalendarGridRowProps = $props() </script> <tr bind:this={ref} data-uipkge data-slot="calendar-grid-row" class={cn('flex', className)} {...restProps}> {@render children?.()} </tr>src/lib/components/ui/calendar/CalendarHeadCell.svelte0.6 kB
<script lang="ts" module> import type { HTMLAttributes } from 'svelte/elements' export interface CalendarHeadCellProps extends HTMLAttributes<HTMLTableCellElement> { ref?: HTMLTableCellElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, children, ref = $bindable(null), ...restProps }: CalendarHeadCellProps = $props() </script> <th bind:this={ref} scope="col" data-uipkge data-slot="calendar-head-cell" class={cn('text-muted-foreground flex-1 rounded-md text-xs font-normal', className)} {...restProps} > {@render children?.()} </th>src/lib/components/ui/calendar/CalendarHeader.svelte0.6 kB
<script lang="ts" module> import type { HTMLAttributes } from 'svelte/elements' export interface CalendarHeaderProps extends HTMLAttributes<HTMLDivElement> { ref?: HTMLDivElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, children, ref = $bindable(null), ...restProps }: CalendarHeaderProps = $props() </script> <div bind:this={ref} data-uipkge data-slot="calendar-header" class={cn('relative flex w-full items-center justify-center px-8 pt-1', className)} {...restProps} > {@render children?.()} </div>src/lib/components/ui/calendar/CalendarHeading.svelte1 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface CalendarHeadingProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> { children?: Snippet<[{ headingValue: string }]> ref?: HTMLDivElement | null } </script> <script lang="ts"> import { getContext } from 'svelte' import { cn } from '$lib/utils' import { CALENDAR_CONTEXT_KEY, type CalendarState } from './calendar-state.svelte' let { class: className, children, ref = $bindable(null), ...restProps }: CalendarHeadingProps = $props() const state = getContext<CalendarState | undefined>(CALENDAR_CONTEXT_KEY) const headingValue = $derived(state?.headingValue ?? '') </script> <div bind:this={ref} data-uipkge data-slot="calendar-heading" class={cn('text-sm font-medium', className)} {...restProps}> {#if children} {@render children({ headingValue })} {:else} {headingValue} {/if} </div>src/lib/components/ui/calendar/CalendarNextButton.svelte1.4 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLButtonAttributes } from 'svelte/elements' export interface CalendarNextButtonProps extends HTMLButtonAttributes { /** Overrides the default chevron icon. */ icon?: Snippet ref?: HTMLButtonElement | null } </script> <script lang="ts"> import { getContext } from 'svelte' import { ChevronRight } from '@lucide/svelte' import { cn } from '$lib/utils' import { Button } from '$lib/components/ui/button' import { buttonVariants } from '$lib/components/ui/button/button.variants' import { CALENDAR_CONTEXT_KEY, type CalendarState } from './calendar-state.svelte' let { class: className, icon, children, ref = $bindable(null), ...restProps }: CalendarNextButtonProps = $props() const state = getContext<CalendarState | undefined>(CALENDAR_CONTEXT_KEY) </script> <Button type="button" variant="outline" bind:ref data-slot="calendar-next-button" aria-label="Next month" disabled={state ? !state.canGoNext : undefined} class={cn( buttonVariants({ variant: 'outline' }), 'size-9 bg-transparent p-0 opacity-70 hover:opacity-100 focus-visible:opacity-100', className, )} onclick={() => state?.goNext()} {...restProps} > {#if icon} {@render icon()} {:else if children} {@render children()} {:else} <ChevronRight class="size-4" aria-hidden="true" /> {/if} </Button>src/lib/components/ui/calendar/CalendarPrevButton.svelte1.4 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLButtonAttributes } from 'svelte/elements' export interface CalendarPrevButtonProps extends HTMLButtonAttributes { /** Overrides the default chevron icon. */ icon?: Snippet ref?: HTMLButtonElement | null } </script> <script lang="ts"> import { getContext } from 'svelte' import { ChevronLeft } from '@lucide/svelte' import { cn } from '$lib/utils' import { Button } from '$lib/components/ui/button' import { buttonVariants } from '$lib/components/ui/button/button.variants' import { CALENDAR_CONTEXT_KEY, type CalendarState } from './calendar-state.svelte' let { class: className, icon, children, ref = $bindable(null), ...restProps }: CalendarPrevButtonProps = $props() const state = getContext<CalendarState | undefined>(CALENDAR_CONTEXT_KEY) </script> <Button type="button" variant="outline" bind:ref data-slot="calendar-prev-button" aria-label="Previous month" disabled={state ? !state.canGoPrev : undefined} class={cn( buttonVariants({ variant: 'outline' }), 'size-9 bg-transparent p-0 opacity-70 hover:opacity-100 focus-visible:opacity-100', className, )} onclick={() => state?.goPrev()} {...restProps} > {#if icon} {@render icon()} {:else if children} {@render children()} {:else} <ChevronLeft class="size-4" aria-hidden="true" /> {/if} </Button>src/lib/components/ui/calendar/NativeSelect.svelte4.4 kB
<script lang="ts" module> import type { HTMLSelectAttributes } from 'svelte/elements' export interface NativeSelectProps extends Omit<HTMLSelectAttributes, 'onchange'> { value?: string | number | string[] label?: string placeholder?: string readonly?: boolean error?: boolean errorMessages?: string | string[] successMessages?: string | string[] hint?: string variant?: 'outlined' | 'filled' | 'solo' | 'underlined' density?: 'compact' | 'default' | 'comfortable' color?: 'primary' | 'secondary' | 'error' | 'success' | 'warning' | 'info' hideDetails?: boolean bgColor?: string onchange?: (value: string | number | string[] | undefined) => void ref?: HTMLSelectElement | null } </script> <script lang="ts"> import { ChevronDown } from '@lucide/svelte' import { cn } from '$lib/utils' const uid = $props.id() let { class: className, value = $bindable<string | number | string[] | undefined>(undefined), label = '', placeholder = '', disabled = false, readonly = false, error = false, errorMessages = [], successMessages = [], hint = '', variant = 'outlined', density = 'default', color: _color = 'primary', hideDetails = false, bgColor, onchange, children, ref = $bindable(null), ...restProps }: NativeSelectProps = $props() const hasError = $derived(error || (Array.isArray(errorMessages) ? errorMessages.length > 0 : !!errorMessages)) const hasSuccess = $derived( !hasError && (Array.isArray(successMessages) ? successMessages.length > 0 : !!successMessages), ) const densityClasses = { compact: 'h-8 text-xs', default: 'h-9 text-sm', comfortable: 'h-10 text-base', } const variantClasses = { outlined: 'border bg-transparent', filled: 'border-b-2 bg-muted/30 border-transparent', solo: 'border bg-card shadow-md', underlined: 'border-b bg-transparent rounded-none border-x-0 border-t-0', } const stateClasses = $derived.by(() => { if (hasError) return 'border-destructive focus-visible:ring-destructive/20' if (hasSuccess) return 'border-success focus-visible:border-success' return 'border-input focus-visible:border-ring focus-visible:ring-ring/20 focus-visible:ring-[3px]' }) </script> <div class={cn('relative w-full', densityClasses[density])}> <!-- Label --> {#if label} <label for={uid} class={cn('mb-1 block text-sm font-medium', hasError && 'text-destructive')}> {label} </label> {/if} <!-- Select Wrapper --> <div class={cn('group/native-select relative w-full', className)}> <!-- `readonly` is accepted for API parity but not rendered: it is invalid on <select> and browsers ignore it (same effective behavior as the Vue twin). --> <select bind:this={ref} bind:value id={uid} data-uipkge data-slot="native-select" disabled={disabled} aria-label={label || placeholder || undefined} class={cn( 'border-input placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 dark:hover:bg-input/50', variantClasses[variant], stateClasses, densityClasses[density], 'w-full min-w-0 appearance-none rounded-md border bg-transparent px-3 pr-9 shadow-xs transition-[color,box-shadow] outline-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50', bgColor, )} onchange={() => onchange?.(value)} {...restProps} > {@render children?.()} </select> <ChevronDown class="text-muted-foreground pointer-events-none absolute top-1/2 right-3.5 size-4 -translate-y-1/2 opacity-50 select-none" aria-hidden="true" data-uipkge data-slot="native-select-icon" /> </div> <!-- Details: hint, error, success --> {#if !hideDetails} <div class={cn('mt-1 text-xs', { 'text-destructive': hasError, 'text-success': hasSuccess, 'text-muted-foreground': !hasError && !hasSuccess, })} > {#if hasError} {#if typeof errorMessages === 'string'} <span>{errorMessages}</span> {:else} <span>{errorMessages[0]}</span> {/if} {:else if hasSuccess} {#if typeof successMessages === 'string'} <span>{successMessages}</span> {:else} <span>{successMessages[0]}</span> {/if} {:else} {hint} {/if} </div> {/if} </div>src/lib/components/ui/calendar/NativeSelectOption.svelte0.6 kB
<script lang="ts" module> import type { HTMLOptionAttributes } from 'svelte/elements' export interface NativeSelectOptionProps extends HTMLOptionAttributes { ref?: HTMLOptionElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, children, ref = $bindable(null), ...restProps }: NativeSelectOptionProps = $props() </script> <option bind:this={ref} data-slot="native-select-option" class={cn('bg-popover text-popover-foreground', className)} {...restProps} > {@render children?.()} </option>src/lib/components/ui/calendar/calendar-state.svelte.ts16.1 kB
/** * Calendar state shared between `Calendar.svelte` (root) and its parts via * Svelte context. Replaces what reka-ui's `CalendarRoot` provided in the Vue * twin: grid computation, selection, min/max + disabled/unavailable gating, * month navigation, and roving-focus keyboard navigation — all hand-rolled * with runes on top of `@internationalized/date`. */ import { tick } from 'svelte' import { CalendarDate, endOfMonth, endOfWeek, getLocalTimeZone, startOfWeek, today, type DateValue, type DayOfWeek, } from '@internationalized/date' export const CALENDAR_CONTEXT_KEY = 'calendar' export type CalendarSelectionType = 'single' | 'multiple' | 'range' /** * Range value for `type="range"` (two-click start/end selection). * * INTENTIONAL DIVERGENCE from React: React's DayPicker-based Calendar uses * native `Date` objects (`{ from: Date, to: Date }`). This Svelte calendar * keeps `@internationalized/date` `DateValue` (CalendarDate) to match the * Vue/reka-ui twin and preserve timezone-safe arithmetic. Convert at the * boundary with `new CalendarDate(d.getFullYear(), d.getMonth() + 1, d.getDate())`. */ export interface CalendarRange { start: DateValue | undefined end: DateValue | undefined } const DAY_OF_WEEK_BY_INDEX: DayOfWeek[] = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'] export interface CalendarGridData { /** First day of the rendered month. */ value: CalendarDate rows: CalendarDate[][] } export interface CalendarStateOptions { onValueChange?: (value: DateValue | DateValue[] | CalendarRange | undefined) => void onPlaceholderChange?: (placeholder: DateValue) => void } function toCalendarDate(date: DateValue): CalendarDate { return new CalendarDate(date.year, date.month, date.day) } function sameDate(a: DateValue | undefined, b: DateValue | undefined): boolean { if (a == null || b == null) return a == null && b == null return a.compare(b) === 0 } function sameDateArray(a: DateValue[], b: DateValue[]): boolean { return a.length === b.length && a.every((d, i) => d.compare(b[i]!) === 0) } function isCalendarRange(v: unknown): v is CalendarRange { return typeof v === 'object' && v !== null && ('start' in v || 'end' in v) } function sameRange(a: CalendarRange | undefined, b: CalendarRange | undefined): boolean { if (a == null || b == null) return a == null && b == null return sameDate(a.start, b.start) && sameDate(a.end, b.end) } export class CalendarState { type = $state<CalendarSelectionType>('single') locale = $state('en') weekStartsOn = $state<number | undefined>(undefined) fixedWeeks = $state(false) numberOfMonths = $state(1) pagedNavigation = $state(false) disabled = $state(false) readonly = $state(false) weekdayFormat = $state<'narrow' | 'short' | 'long'>('narrow') minValue = $state<DateValue | undefined>(undefined) maxValue = $state<DateValue | undefined>(undefined) isDateDisabled = $state<((date: DateValue) => boolean) | undefined>(undefined) isDateUnavailable = $state<((date: DateValue) => boolean) | undefined>(undefined) showOutsideDays = $state(true) placeholder = $state<CalendarDate>(today(getLocalTimeZone())) singleValue = $state<DateValue | undefined>(undefined) multipleValue = $state<DateValue[]>([]) rangeValue = $state<CalendarRange | undefined>(undefined) hoverDate = $state<CalendarDate | undefined>(undefined) focusedDate = $state<CalendarDate>(today(getLocalTimeZone())) rootEl = $state<HTMLElement | null>(null) #options: CalendarStateOptions constructor(initial?: { type?: CalendarSelectionType locale?: string weekStartsOn?: number fixedWeeks?: boolean numberOfMonths?: number pagedNavigation?: boolean disabled?: boolean readonly?: boolean weekdayFormat?: 'narrow' | 'short' | 'long' minValue?: DateValue maxValue?: DateValue isDateDisabled?: (date: DateValue) => boolean isDateUnavailable?: (date: DateValue) => boolean showOutsideDays?: boolean value?: DateValue | DateValue[] | CalendarRange placeholder?: DateValue }, options: CalendarStateOptions = {}) { this.#options = options if (initial) this.syncFromProps(initial) // First tab stop: the selected day when visible, else the viewed month. this.focusedDate = this.initialFocusedDate() } get firstDayOfWeek(): DayOfWeek | undefined { return this.weekStartsOn != null ? (DAY_OF_WEEK_BY_INDEX[this.weekStartsOn] ?? undefined) : undefined } get grids(): CalendarGridData[] { const firstDay = this.firstDayOfWeek const out: CalendarGridData[] = [] const base = new CalendarDate(this.placeholder.year, this.placeholder.month, 1) for (let i = 0; i < Math.max(1, this.numberOfMonths); i++) { const monthStart = base.add({ months: i }) const monthEnd = endOfMonth(monthStart) const start = startOfWeek(monthStart, this.locale, firstDay) const end = endOfWeek(monthEnd, this.locale, firstDay) const rows: CalendarDate[][] = [] let cursor = start let guard = 0 while (cursor.compare(end) <= 0 && guard++ < 64) { const week: CalendarDate[] = [] for (let d = 0; d < 7; d++) { week.push(cursor) cursor = cursor.add({ days: 1 }) } rows.push(week) } if (this.fixedWeeks) { while (rows.length < 6) { const week: CalendarDate[] = [] for (let d = 0; d < 7; d++) { week.push(cursor) cursor = cursor.add({ days: 1 }) } rows.push(week) } } out.push({ value: monthStart, rows }) } return out } get weekDays(): string[] { const start = startOfWeek( new CalendarDate(this.placeholder.year, this.placeholder.month, 1), this.locale, this.firstDayOfWeek, ) const fmt = new Intl.DateTimeFormat(this.locale, { weekday: this.weekdayFormat }) return Array.from({ length: 7 }, (_, i) => fmt.format(new Date(start.year, start.month - 1, start.day + i))) } get headingValue(): string { const first = new CalendarDate(this.placeholder.year, this.placeholder.month, 1) const monthYear = new Intl.DateTimeFormat(this.locale, { month: 'long', year: 'numeric' }) if (this.numberOfMonths <= 1) { return monthYear.format(new Date(first.year, first.month - 1, 1)) } const last = first.add({ months: this.numberOfMonths - 1 }) const firstLabel = monthYear.format(new Date(first.year, first.month - 1, 1)) const lastLabel = monthYear.format(new Date(last.year, last.month - 1, 1)) if (first.year === last.year) { const monthOnly = new Intl.DateTimeFormat(this.locale, { month: 'long' }) return `${monthOnly.format(new Date(first.year, first.month - 1, 1))} – ${lastLabel}` } return `${firstLabel} – ${lastLabel}` } /** Value in the shape the parent binds (`bind:value`). */ get currentValue(): DateValue | DateValue[] | CalendarRange | undefined { if (this.type === 'multiple') return [...this.multipleValue] if (this.type === 'range') return this.rangeValue ? { ...this.rangeValue } : undefined return this.singleValue } isSelected(date: DateValue): boolean { if (this.type === 'multiple') return this.multipleValue.some((v) => v.compare(date) === 0) if (this.type === 'range') { const r = this.rangeValue if (!r?.start) return false if (!r.end) return r.start.compare(date) === 0 return date.compare(r.start) >= 0 && date.compare(r.end) <= 0 } return this.singleValue != null && this.singleValue.compare(date) === 0 } isSelectionStart(date: DateValue): boolean { if (this.type !== 'range') return false const s = this.rangeValue?.start return s != null && s.compare(date) === 0 } isSelectionEnd(date: DateValue): boolean { if (this.type !== 'range') return false const e = this.rangeValue?.end return e != null && e.compare(date) === 0 } /** Hover-preview middle days while a range start is picked but no end yet. */ isHighlighted(date: DateValue): boolean { if (this.type !== 'range') return false const start = this.rangeValue?.start const end = this.rangeValue?.end if (!start || end) return false const hover = this.hoverDate if (!hover || hover.compare(start) === 0) return false const lo = start.compare(hover) <= 0 ? start : hover const hi = lo === start ? hover : start return date.compare(lo) > 0 && date.compare(hi) < 0 } hover(date: CalendarDate | null): void { this.hoverDate = date ?? undefined } isDisabledDate(date: DateValue): boolean { if (this.disabled) return true if (this.minValue && date.compare(this.minValue) < 0) return true if (this.maxValue && date.compare(this.maxValue) > 0) return true return this.isDateDisabled?.(date) ?? false } isUnavailableDate(date: DateValue): boolean { return this.isDateUnavailable?.(date) ?? false } isTodayDate(date: DateValue): boolean { return date.compare(today(getLocalTimeZone())) === 0 } isOutsideView(date: CalendarDate, month: CalendarDate): boolean { return date.year !== month.year || date.month !== month.month } /** The one tabbable day in the roving-tabindex grid. */ isTabbableDate(day: CalendarDate): boolean { return day.compare(this.focusedDate) === 0 } isDateVisible(date: DateValue): boolean { return this.grids.some((g) => g.value.year === date.year && g.value.month === date.month) } get canGoPrev(): boolean { if (!this.minValue) return true const step = this.pagedNavigation ? Math.max(1, this.numberOfMonths) : 1 const target = new CalendarDate(this.placeholder.year, this.placeholder.month, 1).subtract({ months: step }) return target.compare(new CalendarDate(this.minValue.year, this.minValue.month, 1)) >= 0 } get canGoNext(): boolean { if (!this.maxValue) return true const step = this.pagedNavigation ? Math.max(1, this.numberOfMonths) : 1 const target = new CalendarDate(this.placeholder.year, this.placeholder.month, 1).add({ months: step }) return target.compare(new CalendarDate(this.maxValue.year, this.maxValue.month, 1)) <= 0 } select(date: DateValue): void { if (this.disabled || this.readonly) return if (this.isDisabledDate(date) || this.isUnavailableDate(date)) return if (this.type === 'multiple') { const exists = this.multipleValue.some((v) => v.compare(date) === 0) const next = exists ? this.multipleValue.filter((v) => v.compare(date) !== 0) : [...this.multipleValue, date] this.multipleValue = next this.#options.onValueChange?.(next) } else if (this.type === 'range') { const r = this.rangeValue if (!r?.start || (r.start && r.end)) { const next: CalendarRange = { start: date, end: undefined } this.rangeValue = next this.#options.onValueChange?.(next) } else if (date.compare(r.start) < 0) { const next: CalendarRange = { start: date, end: r.start } this.rangeValue = next this.#options.onValueChange?.(next) } else { const next: CalendarRange = { start: r.start, end: date } this.rangeValue = next this.#options.onValueChange?.(next) } this.hoverDate = undefined } else { this.singleValue = date this.#options.onValueChange?.(date) } this.focusedDate = toCalendarDate(date) } setPlaceholder(date: DateValue, opts?: { focus?: CalendarDate }): void { const next = toCalendarDate(date) this.placeholder = next this.focusedDate = opts?.focus ?? next this.#options.onPlaceholderChange?.(next) } goPrev(): void { if (!this.canGoPrev) return const step = this.pagedNavigation ? Math.max(1, this.numberOfMonths) : 1 this.setPlaceholder(this.placeholder.subtract({ months: step })) } goNext(): void { if (!this.canGoNext) return const step = this.pagedNavigation ? Math.max(1, this.numberOfMonths) : 1 this.setPlaceholder(this.placeholder.add({ months: step })) } formatDay(date: DateValue): string { return new Intl.DateTimeFormat(this.locale, { dateStyle: 'full' }).format( new Date(date.year, date.month - 1, date.day), ) } focusDay(date: CalendarDate): void { this.focusedDate = date this.rootEl ?.querySelector<HTMLButtonElement>(`[data-day="${date.toString()}"]`) ?.focus({ preventScroll: true }) } handleDayKeydown(e: KeyboardEvent, day: CalendarDate): void { const firstDay = this.firstDayOfWeek let target: CalendarDate | null = null switch (e.key) { case 'ArrowRight': target = day.add({ days: 1 }) break case 'ArrowLeft': target = day.subtract({ days: 1 }) break case 'ArrowUp': target = day.subtract({ days: 7 }) break case 'ArrowDown': target = day.add({ days: 7 }) break case 'Home': target = startOfWeek(day, this.locale, firstDay) break case 'End': target = endOfWeek(day, this.locale, firstDay) break case 'PageUp': target = e.shiftKey ? day.subtract({ years: 1 }) : day.subtract({ months: 1 }) break case 'PageDown': target = e.shiftKey ? day.add({ years: 1 }) : day.add({ months: 1 }) break default: return } e.preventDefault() if (this.isDateVisible(target)) { this.focusDay(target) } else { // Move the view first, then focus once the new month renders. this.setPlaceholder(new CalendarDate(target.year, target.month, 1), { focus: target }) void tick().then(() => this.focusDay(target as CalendarDate)) } } /** * Inward sync: parent props → state. Every assignment is equality-guarded * so the parent's `$effect` calling this never loops with the outward * `onValueChange` / `onPlaceholderChange` callbacks. */ syncFromProps(p: { type?: CalendarSelectionType locale?: string weekStartsOn?: number fixedWeeks?: boolean numberOfMonths?: number pagedNavigation?: boolean disabled?: boolean readonly?: boolean weekdayFormat?: 'narrow' | 'short' | 'long' minValue?: DateValue maxValue?: DateValue isDateDisabled?: (date: DateValue) => boolean isDateUnavailable?: (date: DateValue) => boolean showOutsideDays?: boolean value?: DateValue | DateValue[] | CalendarRange placeholder?: DateValue }): void { this.type = p.type ?? 'single' this.locale = p.locale ?? 'en' this.weekStartsOn = p.weekStartsOn this.fixedWeeks = p.fixedWeeks ?? false this.numberOfMonths = p.numberOfMonths ?? 1 this.pagedNavigation = p.pagedNavigation ?? false this.disabled = p.disabled ?? false this.readonly = p.readonly ?? false this.weekdayFormat = p.weekdayFormat ?? 'narrow' this.minValue = p.minValue this.maxValue = p.maxValue this.isDateDisabled = p.isDateDisabled this.isDateUnavailable = p.isDateUnavailable this.showOutsideDays = p.showOutsideDays ?? true if (this.type === 'range') { const r = isCalendarRange(p.value) ? p.value : undefined if (!sameRange(this.rangeValue, r)) this.rangeValue = r ? { start: r.start, end: r.end } : undefined } else if (this.type === 'multiple') { const arr = isCalendarRange(p.value) ? [] : Array.isArray(p.value) ? p.value : p.value ? [p.value] : [] if (!sameDateArray(this.multipleValue, arr)) this.multipleValue = [...arr] } else { const v = isCalendarRange(p.value) ? undefined : Array.isArray(p.value) ? p.value[0] : p.value if (!sameDate(this.singleValue, v)) this.singleValue = v } const ph = p.placeholder ? toCalendarDate(p.placeholder) : today(getLocalTimeZone()) if (this.placeholder.compare(ph) !== 0) { this.placeholder = ph this.focusedDate = this.initialFocusedDate() } } private initialFocusedDate(): CalendarDate { const single = this.type === 'single' ? this.singleValue : this.type === 'range' ? this.rangeValue?.start : this.multipleValue[0] if (single) { const asDate = toCalendarDate(single) if (this.isDateVisible(asDate)) return asDate } const now = today(getLocalTimeZone()) if (this.isDateVisible(now)) return now return toCalendarDate(this.placeholder) } }src/lib/components/ui/calendar/index.ts1.6 kB
export { default as Calendar, type CalendarProps } from './Calendar.svelte' export { default as CalendarCell, type CalendarCellProps } from './CalendarCell.svelte' export { default as CalendarCellTrigger, type CalendarCellTriggerProps } from './CalendarCellTrigger.svelte' export { default as CalendarGrid, type CalendarGridProps } from './CalendarGrid.svelte' export { default as CalendarGridBody, type CalendarGridBodyProps } from './CalendarGridBody.svelte' export { default as CalendarGridHead, type CalendarGridHeadProps } from './CalendarGridHead.svelte' export { default as CalendarGridRow, type CalendarGridRowProps } from './CalendarGridRow.svelte' export { default as CalendarHeadCell, type CalendarHeadCellProps } from './CalendarHeadCell.svelte' export { default as CalendarHeader, type CalendarHeaderProps } from './CalendarHeader.svelte' export { default as CalendarHeading, type CalendarHeadingProps } from './CalendarHeading.svelte' export { default as CalendarNextButton, type CalendarNextButtonProps } from './CalendarNextButton.svelte' export { default as CalendarPrevButton, type CalendarPrevButtonProps } from './CalendarPrevButton.svelte' export { default as NativeSelect, type NativeSelectProps } from './NativeSelect.svelte' export { default as NativeSelectOption, type NativeSelectOptionProps } from './NativeSelectOption.svelte' export { CalendarState, CALENDAR_CONTEXT_KEY, type CalendarRange, type CalendarSelectionType, type CalendarGridData, } from './calendar-state.svelte' export type LayoutTypes = 'month-and-year' | 'month-only' | 'year-only' | undefined
Raw manifest:https://uipkge.dev/r/svelte/calendar.json