{
	"$schema": "https://shadcn-svelte.com/schema/registry-item.json",
	"name": "big-calendar",
	"title": "Big Calendar",
	"type": "registry:ui",
	"description": "A full-sized, interactive calendar with month view and event items.",
	"dependencies": [
		"@lucide/svelte@^0.554.0"
	],
	"devDependencies": [
		"@lucide/svelte@^0.544.0"
	],
	"registryDependencies": [
		"button",
		"input",
		"label"
	],
	"files": [
		{
			"content": "<script lang=\"ts\">\n\timport { cn } from '$UTILS$';\n\timport { setCalendarContext, type CalendarEvent, type DateRange } from './ctx';\n\timport type { Snippet } from 'svelte';\n\n\tlet {\n\t\tclass: className,\n\t\tdate = $bindable(new Date()),\n\t\tselected = $bindable({ start: undefined, end: undefined }),\n\t\tevents = [],\n\t\tweekStartsOn = 1,\n\t\tshowWeekends = true,\n\t\tisDateDisabled = () => false,\n\t\tchildren\n\t}: {\n\t\tclass?: string;\n\t\tdate?: Date;\n\t\tselected?: DateRange;\n\t\tevents?: CalendarEvent[];\n\t\tweekStartsOn?: 0 | 1;\n\t\tshowWeekends?: boolean;\n\t\tisDateDisabled?: (date: Date) => boolean;\n\t\tchildren: Snippet;\n\t} = $props();\n\n\tlet isDragging = $state(false);\n\n\tlet layoutMap = $derived.by(() => {\n\t\tconst map = new Map<string, number>();\n\t\tconst takenSlots = new Map<string, number[]>();\n\n\t\tconst sorted = [...events].sort((a, b) => {\n\t\t\tconst lenA = a.end.getTime() - a.start.getTime();\n\t\t\tconst lenB = b.end.getTime() - b.start.getTime();\n\t\t\tif (lenA !== lenB) return lenB - lenA;\n\t\t\treturn a.start.getTime() - b.start.getTime();\n\t\t});\n\n\t\tconst getKey = (d: Date) => `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;\n\n\t\tfor (const ev of sorted) {\n\t\t\tlet s = new Date(ev.start);\n\t\t\ts.setHours(0, 0, 0, 0);\n\t\t\tlet e = new Date(ev.end);\n\t\t\te.setHours(0, 0, 0, 0);\n\n\t\t\tlet row = 0;\n\t\t\tlet isRowFree = false;\n\n\t\t\twhile (!isRowFree) {\n\t\t\t\tisRowFree = true;\n\t\t\t\tlet curr = new Date(s);\n\t\t\t\twhile (curr <= e) {\n\t\t\t\t\tconst key = getKey(curr);\n\t\t\t\t\tconst taken = takenSlots.get(key) || [];\n\t\t\t\t\tif (taken.includes(row)) {\n\t\t\t\t\t\tisRowFree = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcurr.setDate(curr.getDate() + 1);\n\t\t\t\t}\n\t\t\t\tif (!isRowFree) row++;\n\t\t\t}\n\n\t\t\tlet curr = new Date(s);\n\t\t\twhile (curr <= e) {\n\t\t\t\tconst key = getKey(curr);\n\t\t\t\tconst taken = takenSlots.get(key) || [];\n\t\t\t\ttaken.push(row);\n\t\t\t\ttakenSlots.set(key, taken);\n\n\t\t\t\tmap.set(`${key}::${ev.id}`, row);\n\n\t\t\t\tcurr.setDate(curr.getDate() + 1);\n\t\t\t}\n\t\t}\n\t\treturn map;\n\t});\n\n\tfunction getEventSlot(d: Date, id: string) {\n\t\tconst key = `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}::${id}`;\n\t\treturn layoutMap.get(key) ?? 0;\n\t}\n\n\tfunction setDate(d: Date) {\n\t\tdate = d;\n\t}\n\n\tfunction startSelection(d: Date, e?: MouseEvent) {\n\t\tif (isDateDisabled(d)) return;\n\t\tif (e?.shiftKey && selected.start) {\n\t\t\tisDragging = false;\n\t\t\tconst currentStart = selected.start;\n\t\t\tconst newEnd = d;\n\t\t\tselected =\n\t\t\t\tnewEnd < currentStart\n\t\t\t\t\t? { start: newEnd, end: currentStart }\n\t\t\t\t\t: { start: currentStart, end: newEnd };\n\t\t\treturn;\n\t\t}\n\t\tisDragging = true;\n\t\tselected = { start: d, end: d };\n\t}\n\n\tfunction updateSelection(d: Date) {\n\t\tif (isDateDisabled(d) || !isDragging || !selected.start) return;\n\t\tselected = { ...selected, end: d };\n\t}\n\n\tfunction endSelection() {\n\t\tisDragging = false;\n\t\tif (selected.start && selected.end && selected.start > selected.end) {\n\t\t\tselected = { start: selected.end, end: selected.start };\n\t\t}\n\t}\n\n\tfunction next() {\n\t\tconst d = new Date(date);\n\t\td.setMonth(d.getMonth() + 1);\n\t\tdate = d;\n\t}\n\tfunction prev() {\n\t\tconst d = new Date(date);\n\t\td.setMonth(d.getMonth() - 1);\n\t\tdate = d;\n\t}\n\tfunction today() {\n\t\tdate = new Date();\n\t}\n\n\tsetCalendarContext({\n\t\tcurrentDate: () => date,\n\t\tselectedRange: () => selected,\n\t\tevents: () => events,\n\t\tweekStartsOn: () => weekStartsOn,\n\t\tshowWeekends: () => showWeekends,\n\t\tisDragging: () => isDragging,\n\t\tisDateDisabled,\n\t\tgetEventSlot,\n\n\t\tsetDate,\n\t\tstartSelection,\n\t\tupdateSelection,\n\t\tendSelection,\n\t\tnext,\n\t\tprev,\n\t\ttoday\n\t});\n</script>\n\n<svelte:window onmouseup={endSelection} />\n\n<div\n\tclass={cn(\n\t\t'flex flex-col h-full w-full bg-background border rounded-lg shadow-sm overflow-hidden select-none',\n\t\tclassName\n\t)}\n>\n\t{@render children()}\n</div>\n",
			"type": "registry:ui",
			"target": "big-calendar/big-calendar.svelte"
		},
		{
			"content": "<script lang=\"ts\">\r\n\timport { cn } from '$UTILS$';\r\n\timport { Button } from '$lib/components/ui/button';\r\n\timport { ChevronLeft, ChevronRight, Calendar as CalIcon } from '@lucide/svelte';\r\n\timport { getCalendarContext } from './ctx';\r\n\timport type { Snippet } from 'svelte';\r\n\r\n\tlet {\r\n\t\tclass: className,\r\n\t\tchildren\r\n\t}: {\r\n\t\tclass?: string;\r\n\t\tchildren?: Snippet<\r\n\t\t\t[\r\n\t\t\t\t{\r\n\t\t\t\t\tnext: () => void;\r\n\t\t\t\t\tprev: () => void;\r\n\t\t\t\t\ttoday: () => void;\r\n\t\t\t\t\tcurrentDate: Date;\r\n\t\t\t\t}\r\n\t\t\t]\r\n\t\t>;\r\n\t} = $props();\r\n\r\n\tconst ctx = getCalendarContext();\r\n\r\n\tlet label = $derived(\r\n\t\tctx.currentDate().toLocaleDateString('en-US', { month: 'long', year: 'numeric' })\r\n\t);\r\n</script>\r\n\r\n<div\r\n\tclass={cn('flex items-center justify-between px-6 py-4 border-b bg-card min-h-[72px]', className)}\r\n>\r\n\t{#if children}\r\n\t\t{@render children({\r\n\t\t\tnext: ctx.next,\r\n\t\t\tprev: ctx.prev,\r\n\t\t\ttoday: ctx.today,\r\n\t\t\tcurrentDate: ctx.currentDate()\r\n\t\t})}\r\n\t{:else}\r\n\t\t<div class=\"flex items-center gap-4\">\r\n\t\t\t<div class=\"p-2 bg-primary/10 rounded-md hidden sm:block\">\r\n\t\t\t\t<CalIcon class=\"h-5 w-5 text-primary\" />\r\n\t\t\t</div>\r\n\t\t\t<h2 class=\"text-xl font-bold tracking-tight\">{label}</h2>\r\n\t\t</div>\r\n\t\t<div class=\"flex items-center gap-1\">\r\n\t\t\t<Button variant=\"outline\" size=\"icon\" class=\"h-8 w-8\" onclick={ctx.prev}\r\n\t\t\t\t><ChevronLeft class=\"h-4 w-4\" /></Button\r\n\t\t\t>\r\n\t\t\t<Button variant=\"outline\" class=\"h-8 px-4 text-xs font-medium\" onclick={ctx.today}\r\n\t\t\t\t>Today</Button\r\n\t\t\t>\r\n\t\t\t<Button variant=\"outline\" size=\"icon\" class=\"h-8 w-8\" onclick={ctx.next}\r\n\t\t\t\t><ChevronRight class=\"h-4 w-4\" /></Button\r\n\t\t\t>\r\n\t\t</div>\r\n\t{/if}\r\n</div>\r\n",
			"type": "registry:ui",
			"target": "big-calendar/big-calendar-header.svelte"
		},
		{
			"content": "<script lang=\"ts\">\n\timport { cn } from '$UTILS$';\n\timport { getCalendarContext, type CalendarEvent } from './ctx';\n\timport type { Snippet } from 'svelte';\n\n\tlet {\n\t\tclass: className,\n\t\tchildren\n\t}: {\n\t\tclass?: string;\n\t\tchildren?: Snippet<[CalendarEvent, { isStart: boolean; isEnd: boolean; isMiddle: boolean }]>;\n\t} = $props();\n\n\tconst ctx = getCalendarContext();\n\tconst todayDate = new Date();\n\tconst ROW_HEIGHT = 28;\n\n\tfunction getDaysInMonth(d: Date, startDay: 0 | 1, showWeekends: boolean) {\n\t\tconst year = d.getFullYear();\n\t\tconst month = d.getMonth();\n\t\tconst firstDayOfMonth = new Date(year, month, 1);\n\t\tconst lastDayOfMonth = new Date(year, month + 1, 0);\n\n\t\tconst days = [];\n\t\tlet dayOfWeek = firstDayOfMonth.getDay();\n\t\tlet diff = (dayOfWeek - startDay + 7) % 7;\n\n\t\tconst prevMonthLastDay = new Date(year, month, 0).getDate();\n\n\t\tfor (let i = diff - 1; i >= 0; i--) {\n\t\t\tdays.push({\n\t\t\t\tdate: new Date(year, month - 1, prevMonthLastDay - i),\n\t\t\t\tisCurrentMonth: false\n\t\t\t});\n\t\t}\n\n\t\tfor (let i = 1; i <= lastDayOfMonth.getDate(); i++) {\n\t\t\tdays.push({\n\t\t\t\tdate: new Date(year, month, i),\n\t\t\t\tisCurrentMonth: true\n\t\t\t});\n\t\t}\n\n\t\tconst remaining = 7 - (days.length % 7);\n\t\tif (remaining < 7) {\n\t\t\tfor (let i = 1; i <= remaining; i++) {\n\t\t\t\tdays.push({\n\t\t\t\t\tdate: new Date(year, month + 1, i),\n\t\t\t\t\tisCurrentMonth: false\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (!showWeekends) {\n\t\t\treturn days.filter((d) => {\n\t\t\t\tconst day = d.date.getDay();\n\t\t\t\treturn day !== 0 && day !== 6;\n\t\t\t});\n\t\t}\n\t\treturn days;\n\t}\n\n\tfunction isSameDay(d1: Date, d2: Date) {\n\t\treturn (\n\t\t\td1.getDate() === d2.getDate() &&\n\t\t\td1.getMonth() === d2.getMonth() &&\n\t\t\td1.getFullYear() === d2.getFullYear()\n\t\t);\n\t}\n\n\tfunction isInRange(date: Date) {\n\t\tconst range = ctx.selectedRange();\n\t\tif (!range.start || !range.end) return false;\n\t\tconst start = range.start < range.end ? range.start : range.end;\n\t\tconst end = range.start < range.end ? range.end : range.start;\n\t\tconst d = new Date(date).setHours(0, 0, 0, 0);\n\t\tconst s = new Date(start).setHours(0, 0, 0, 0);\n\t\tconst e = new Date(end).setHours(0, 0, 0, 0);\n\t\treturn d >= s && d <= e;\n\t}\n\n\tfunction isEventOnDay(event: CalendarEvent, day: Date) {\n\t\tconst d = new Date(day).setHours(0, 0, 0, 0);\n\t\tconst s = new Date(event.start).setHours(0, 0, 0, 0);\n\t\tconst e = new Date(event.end).setHours(0, 0, 0, 0);\n\t\treturn d >= s && d <= e;\n\t}\n\n\tlet allDays = $derived(getDaysInMonth(ctx.currentDate(), ctx.weekStartsOn(), ctx.showWeekends()));\n\tlet weekDays = $derived.by(() => {\n\t\tconst base = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];\n\t\tlet days = ctx.weekStartsOn() === 1 ? [...base.slice(1), base[0]] : base;\n\t\tif (!ctx.showWeekends()) return days.filter((d) => d !== 'Sat' && d !== 'Sun');\n\t\treturn days;\n\t});\n\tlet cols = $derived(ctx.showWeekends() ? 7 : 5);\n</script>\n\n<div class={cn('flex-1 flex flex-col min-h-0', className)}>\n\t<div\n\t\tclass=\"grid border-b bg-muted/30\"\n\t\tstyle=\"grid-template-columns: repeat({cols}, minmax(0, 1fr));\"\n\t>\n\t\t{#each weekDays as day}\n\t\t\t<div\n\t\t\t\tclass=\"py-3 text-center text-xs font-semibold uppercase text-muted-foreground tracking-wider border-r last:border-r-0\"\n\t\t\t>\n\t\t\t\t{day}\n\t\t\t</div>\n\t\t{/each}\n\t</div>\n\n\t<div\n\t\tclass=\"grid flex-1 auto-rows-fr\"\n\t\tstyle=\"grid-template-columns: repeat({cols}, minmax(0, 1fr));\"\n\t>\n\t\t{#each allDays as day, i}\n\t\t\t{@const isToday = isSameDay(day.date, todayDate)}\n\t\t\t{@const inRange = isInRange(day.date)}\n\t\t\t{@const isDisabled = ctx.isDateDisabled(day.date)}\n\t\t\t{@const isStart =\n\t\t\t\tctx.selectedRange().start && isSameDay(day.date, ctx.selectedRange().start!)}\n\t\t\t{@const isEnd = ctx.selectedRange().end && isSameDay(day.date, ctx.selectedRange().end!)}\n\t\t\t{@const dayEvents = ctx.events().filter((e) => isEventOnDay(e, day.date))}\n\n\t\t\t<div\n\t\t\t\tclass={cn(\n\t\t\t\t\t'relative border-b border-r transition-colors flex flex-col min-h-[80px]',\n\t\t\t\t\t!day.isCurrentMonth && 'bg-muted/5 text-muted-foreground/40',\n\t\t\t\t\tday.isCurrentMonth && 'bg-background',\n\t\t\t\t\tinRange && 'bg-primary/5',\n\t\t\t\t\t(i + 1) % cols === 0 && 'border-r-0',\n\t\t\t\t\tisDisabled && 'bg-muted/10 cursor-not-allowed opacity-60'\n\t\t\t\t)}\n\t\t\t\tonmousedown={(e) => !isDisabled && ctx.startSelection(day.date, e)}\n\t\t\t\tonmouseenter={() => !isDisabled && ctx.updateSelection(day.date)}\n\t\t\t\tonmouseup={() => ctx.endSelection()}\n\t\t\t>\n\t\t\t\t<div class=\"flex justify-between items-start p-2 pb-1 relative z-0\">\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass={cn(\n\t\t\t\t\t\t\t'text-xs font-medium h-6 w-6 flex items-center justify-center rounded-full transition-colors relative z-10',\n\t\t\t\t\t\t\tisToday ? 'bg-primary text-primary-foreground' : 'text-muted-foreground',\n\t\t\t\t\t\t\t(isStart || isEnd) && !isToday && 'bg-primary text-primary-foreground font-bold',\n\t\t\t\t\t\t\tisDisabled && 'text-muted-foreground/30'\n\t\t\t\t\t\t)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{day.date.getDate()}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\n\t\t\t\t<div class=\"relative flex-1 w-full overflow-y-clip hover:overflow-y-visible z-10\">\n\t\t\t\t\t{#each dayEvents as event (event.id)}\n\t\t\t\t\t\t{@const isEventStart = isSameDay(event.start, day.date)}\n\t\t\t\t\t\t{@const isEventEnd = isSameDay(event.end, day.date)}\n\t\t\t\t\t\t{@const slotIndex = ctx.getEventSlot(day.date, event.id)}\n\n\t\t\t\t\t\t{@const isWeekStart = i % cols === 0}\n\t\t\t\t\t\t{@const isWeekEnd = (i + 1) % cols === 0}\n\n\t\t\t\t\t\t{@const visualStart = isEventStart || isWeekStart}\n\t\t\t\t\t\t{@const visualEnd = isEventEnd || isWeekEnd}\n\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclass=\"absolute w-full px-0 transition-all duration-200 ease-in-out\"\n\t\t\t\t\t\t\tstyle=\"top: {slotIndex * ROW_HEIGHT}px; height: {ROW_HEIGHT}px;\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{#if children}\n\t\t\t\t\t\t\t\t{@render children(event, {\n\t\t\t\t\t\t\t\t\tisStart: visualStart,\n\t\t\t\t\t\t\t\t\tisEnd: visualEnd,\n\t\t\t\t\t\t\t\t\tisMiddle: !visualStart && !visualEnd\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t{/each}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t{/each}\n\t</div>\n</div>\n\n<style>\n\tdiv::-webkit-scrollbar {\n\t\tdisplay: none;\n\t}\n\tdiv {\n\t\t-ms-overflow-style: none;\n\t\tscrollbar-width: none;\n\t}\n</style>\n",
			"type": "registry:ui",
			"target": "big-calendar/big-calendar-grid.svelte"
		},
		{
			"content": "<script lang=\"ts\">\n\timport { cn } from '$UTILS$';\n\timport type { Snippet } from 'svelte';\n\n\tlet {\n\t\tclass: className,\n\t\tchildren,\n\t\ttitle,\n\t\tvariant = 'default',\n\n\t\tisStart = true,\n\t\tisEnd = true,\n\t\tisMiddle = false,\n\n\t\t...rest\n\t}: {\n\t\tclass?: string;\n\t\tchildren?: Snippet;\n\t\ttitle?: string;\n\t\tvariant?: 'default' | 'secondary' | 'destructive' | 'success' | 'warning';\n\t\tisStart?: boolean;\n\t\tisEnd?: boolean;\n\t\tisMiddle?: boolean;\n\t\t[key: string]: any;\n\t} = $props();\n\n\tconst variants = {\n\t\tdefault: 'bg-primary/10 text-primary border-primary/20 hover:bg-primary/20',\n\t\tsecondary: 'bg-muted text-foreground border-border hover:bg-muted/80',\n\t\tdestructive:\n\t\t\t'bg-red-50 text-red-700 border-red-200 dark:bg-red-900/20 dark:text-red-300 dark:border-red-800',\n\t\tsuccess:\n\t\t\t'bg-green-50 text-green-700 border-green-200 dark:bg-green-900/20 dark:text-green-300 dark:border-green-800',\n\t\twarning:\n\t\t\t'bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-900/20 dark:text-amber-300 dark:border-amber-800'\n\t};\n</script>\n\n<div\n\tclass={cn(\n\t\t'group relative flex w-full items-center h-6 px-2 text-xs font-medium border-y transition-all cursor-pointer shadow-sm z-10',\n\t\tvariants[variant],\n\n\t\tisStart && isEnd && 'rounded-md mx-1 w-[calc(100%-8px)] border-x',\n\n\t\tisStart && !isEnd && 'rounded-l-md ml-1 mr-[-1px] w-[calc(100%-4px)] border-l z-20',\n\n\t\tisMiddle && 'rounded-none mx-[-1px] w-[calc(100%+2px)] border-x-0 z-10',\n\n\t\t!isStart && isEnd && 'rounded-r-md mr-1 ml-[-1px] w-[calc(100%-4px)] border-r z-20',\n\n\t\tclassName\n\t)}\n\t{...rest}\n>\n\t<div class={cn('flex items-center gap-2 w-full overflow-hidden', !isStart && 'opacity-0')}>\n\t\t<div class=\"h-1.5 w-1.5 rounded-full bg-current shrink-0 opacity-70\" />\n\t\t<span class=\"truncate flex-1 text-left\">{title}</span>\n\t\t{@render children?.()}\n\t</div>\n</div>\n",
			"type": "registry:ui",
			"target": "big-calendar/big-calendar-item.svelte"
		},
		{
			"content": "import { getContext, setContext } from 'svelte';\n\nconst CALENDAR_KEY = Symbol('big-calendar');\n\nexport type CalendarEvent = {\n\tid: string;\n\ttitle: string;\n\tstart: Date;\n\tend: Date;\n\ttype?: 'default' | 'secondary' | 'destructive' | 'success' | 'warning';\n\t[key: string]: any;\n};\n\nexport type DateRange = {\n\tstart: Date | undefined;\n\tend: Date | undefined;\n};\n\ntype CalendarContext = {\n\tcurrentDate: () => Date;\n\tselectedRange: () => DateRange;\n\tevents: () => CalendarEvent[];\n\tweekStartsOn: () => 0 | 1;\n\tshowWeekends: () => boolean;\n\tisDragging: () => boolean;\n\tisDateDisabled: (d: Date) => boolean;\n\n\tgetEventSlot: (date: Date, eventId: string) => number;\n\n\tsetDate: (d: Date) => void;\n\tstartSelection: (d: Date, e?: MouseEvent) => void;\n\tupdateSelection: (d: Date) => void;\n\tendSelection: () => void;\n\tnext: () => void;\n\tprev: () => void;\n\ttoday: () => void;\n};\n\nexport function setCalendarContext(props: CalendarContext) {\n\tsetContext(CALENDAR_KEY, props);\n}\n\nexport function getCalendarContext() {\n\treturn getContext<CalendarContext>(CALENDAR_KEY);\n}\n",
			"type": "registry:ui",
			"target": "big-calendar/ctx.ts"
		},
		{
			"content": "import Root from './big-calendar.svelte';\nimport Header from './big-calendar-header.svelte';\nimport Grid from './big-calendar-grid.svelte';\nimport Item from './big-calendar-item.svelte';\n\nexport { Root, Header, Grid, Item };\n",
			"type": "registry:ui",
			"target": "big-calendar/index.ts"
		}
	]
}