{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "calendar-card",
  "type": "registry:ui",
  "title": "Calendar Card",
  "description": "A tactile calendar with animated date selection, event dots, hover event popups and an optional event creation form.",
  "dependencies": [
    "gsap",
    "@gsap/react",
    "lucide-react",
    "@radix-ui/react-dialog"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/ui/calendar-card.tsx",
      "content": "\"use client\";\n\nimport {\n  useEffect,\n  useId,\n  useRef,\n  useState,\n  useSyncExternalStore,\n  type KeyboardEvent,\n  type CSSProperties,\n} from \"react\";\nimport { useGSAP } from \"@gsap/react\";\nimport gsap from \"gsap\";\nimport { ChevronDown, ChevronLeft, ChevronRight, Plus, X } from \"lucide-react\";\nimport * as Dialog from \"@radix-ui/react-dialog\";\nimport { cn } from \"@/lib/utils\";\n\ngsap.registerPlugin(useGSAP);\n\nexport interface CalendarEvent {\n  id: string;\n  date: string;\n  title: string;\n  time?: string;\n  color?: string;\n  assignee?: string;\n}\n\nexport interface CalendarCardProps {\n  value?: Date;\n  defaultValue?: Date;\n  onChange?: (date: Date) => void;\n  className?: string;\n  events?: CalendarEvent[];\n  onEventClick?: (event: CalendarEvent) => void;\n  onEventAdd?: (event: CalendarEvent) => void;\n  accentColor?: string;\n  density?: \"comfortable\" | \"compact\";\n  weekStartsOn?: 0 | 1;\n  showEvents?: boolean;\n  showProgress?: boolean;\n}\n\nconst months = [\n  \"January\", \"February\", \"March\", \"April\", \"May\", \"June\",\n  \"July\", \"August\", \"September\", \"October\", \"November\", \"December\",\n];\nconst weekdays = [\n  \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\", \"Sunday\",\n];\nconst subscribe = () => () => {};\nconst readToday = () => new Date().setHours(0, 0, 0, 0);\nconst serverToday = () => 0;\n\nfunction inMonth(date: Date, year: number, month: number) {\n  return new Date(\n    year,\n    month,\n    Math.min(date.getDate(), new Date(year, month + 1, 0).getDate()),\n  );\n}\n\nexport function CalendarCard({\n  value, defaultValue, onChange, className, events = [], onEventClick, onEventAdd,\n  accentColor = \"#ff675c\", density = \"comfortable\", weekStartsOn = 1,\n  showEvents = true, showProgress = true,\n}: CalendarCardProps) {\n  const today = useSyncExternalStore(subscribe, readToday, serverToday);\n  const [internalDate, setInternalDate] = useState(defaultValue);\n  const date = value ?? internalDate ?? new Date(today);\n  const ready = Boolean(value || internalDate || today);\n  const year = date.getFullYear();\n  const month = date.getMonth();\n  const day = date.getDate();\n  const [pickerYear, setPickerYear] = useState<number | null>(null);\n  const rootRef = useRef<HTMLDivElement>(null);\n  const popupRef = useRef<HTMLDivElement>(null);\n  const eventTitleRef = useRef<HTMLInputElement>(null);\n  const eventCounter = useRef(0);\n  const popupAnchor = useRef<HTMLButtonElement | null>(null);\n  const closePopupTween = useRef<gsap.core.Tween | null>(null);\n  const [popupDay, setPopupDay] = useState<number | null>(null);\n  const [eventFormDate, setEventFormDate] = useState<string | null>(null);\n  const titleRef = useRef<HTMLButtonElement>(null);\n  const keyboardFocus = useRef(false);\n  const previousNumbers = useRef({ day, year: pickerYear });\n  const previousHighlight = useRef<HTMLElement | null>(null);\n  const panelId = useId();\n  const popupId = useId();\n  const count = new Date(year, month + 1, 0).getDate();\n  const offset = (new Date(year, month, 1).getDay() - weekStartsOn + 7) % 7;\n  const week = weekStartsOn === 1 ? weekdays : [weekdays[6], ...weekdays.slice(0, 6)];\n  const monthKey = `${year}-${String(month + 1).padStart(2, \"0\")}`;\n  const eventsByDay = new Map<number, CalendarEvent[]>();\n  for (const event of events) {\n    if (!event.date.startsWith(`${monthKey}-`)) continue;\n    const eventDay = Number(event.date.slice(monthKey.length + 1));\n    if (event.date !== `${monthKey}-${String(eventDay).padStart(2, \"0\")}` || eventDay < 1 || eventDay > count) continue;\n    const entries = eventsByDay.get(eventDay) ?? [];\n    entries.push(event);\n    eventsByDay.set(eventDay, entries);\n  }\n  const selectedEvents = eventsByDay.get(day) ?? [];\n  const popupEvents = eventsByDay.get(popupDay ?? 0) ?? [];\n  const popupOpen = showEvents && pickerYear === null && popupDay !== null && popupEvents.length > 0;\n  const dateKey = `${monthKey}-${String(day).padStart(2, \"0\")}`;\n  const EventTag = onEventClick ? \"button\" : \"div\";\n  const selectedLabel = `${months[month]} ${day}, ${year}`;\n  const buttonClass = \"cursor-pointer rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-[var(--calendar-accent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[#191919]\";\n\n  useGSAP(\n    () => {\n      if (!ready) return;\n      const media = gsap.matchMedia();\n      media.add(\"(prefers-reduced-motion: no-preference)\", () => {\n        gsap.from(\"[data-calendar-panel]\", {\n          y: 8, opacity: 0, duration: 0.28,\n          ease: \"power3.out\", clearProps: \"transform,opacity\",\n        });\n      }, rootRef);\n      return () => media.revert();\n    },\n    {\n      scope: rootRef,\n      dependencies: [year, month, pickerYear !== null, ready],\n      revertOnUpdate: true,\n    },\n  );\n\n  useGSAP(() => {\n    const previous = previousNumbers.current;\n    previousNumbers.current = { day, year: pickerYear };\n    if (!ready) return;\n    const media = gsap.matchMedia();\n    media.add(\"(prefers-reduced-motion: no-preference)\", () => {\n      for (const [name, before, after] of [\n        [\"day\", previous.day, day],\n        [\"year\", previous.year, pickerYear],\n      ] as const) {\n        if (before === null || after === null || before === after) continue;\n        const direction = after > before ? 1 : -1;\n        const duration = name === \"year\" ? 0.38 : 0.3;\n        const slots = rootRef.current?.querySelectorAll<HTMLElement>(\n          name === \"year\" ? \"[data-calendar-year-digit]\" : \"[data-calendar-day-number]\",\n        );\n        slots?.forEach((slot) => {\n          const place = Number(slot.dataset.calendarYearDigit ?? 0);\n          const oldText = name === \"year\" ? String(before).at(-place - 1) ?? \"\" : String(before);\n          const newText = name === \"year\" ? String(after).at(-place - 1) ?? \"\" : String(after);\n          if (oldText === newText) return;\n          const outgoing = slot.querySelector<HTMLElement>(`[data-calendar-${name}-out]`);\n          const incoming = slot.querySelector<HTMLElement>(`[data-calendar-${name}]`);\n          if (!outgoing || !incoming) return;\n          outgoing.textContent = oldText;\n          gsap.fromTo(outgoing,\n            { yPercent: 0, opacity: 1 },\n            { yPercent: -direction * 100, opacity: 0, duration, ease: \"power3.out\" },\n          );\n          gsap.fromTo(incoming,\n            { yPercent: direction * 100, opacity: 0 },\n            { yPercent: 0, opacity: 1, duration, ease: \"power3.out\" },\n          );\n        });\n      }\n    }, rootRef);\n    return () => media.revert();\n  }, { scope: rootRef, dependencies: [day, pickerYear, ready], revertOnUpdate: true });\n\n  useGSAP(() => {\n    const grid = rootRef.current?.querySelector<HTMLElement>(\"[data-calendar-grid]\");\n    const hover = grid?.querySelector<HTMLElement>(\"[data-calendar-hover]\");\n    if (!grid || !hover) return;\n    const media = gsap.matchMedia();\n    media.add({ reduced: \"(prefers-reduced-motion: reduce)\", standard: \"(prefers-reduced-motion: no-preference)\" }, (context) => {\n      const duration = context.conditions?.reduced ? 0 : 0.24;\n      const xTo = gsap.quickTo(hover, \"x\", { duration, ease: \"power3.out\" });\n      const yTo = gsap.quickTo(hover, \"y\", { duration, ease: \"power3.out\" });\n      const opacityTo = gsap.quickTo(hover, \"opacity\", { duration: duration * 0.75 });\n      let hovered: HTMLElement | null = null;\n      const hide = () => { hovered = null; opacityTo(0); };\n      const move = (event: PointerEvent) => {\n        if (event.pointerType === \"touch\") return;\n        const target = (event.target as HTMLElement).closest<HTMLElement>(\"[data-calendar-date]\");\n        if (!target || !grid.contains(target)) return;\n        const first = !hovered;\n        hovered = target;\n        xTo(target.offsetLeft);\n        yTo(target.offsetTop);\n        if (first) {\n          xTo.tween.progress(1);\n          yTo.tween.progress(1);\n        }\n        opacityTo(1);\n      };\n      let width = grid.clientWidth;\n      const observer = new ResizeObserver(() => {\n        if (grid.clientWidth === width) return;\n        width = grid.clientWidth;\n        hide();\n      });\n      observer.observe(grid);\n      grid.addEventListener(\"pointerover\", move);\n      grid.addEventListener(\"pointerleave\", hide);\n      return () => {\n        observer.disconnect();\n        grid.removeEventListener(\"pointerover\", move);\n        grid.removeEventListener(\"pointerleave\", hide);\n      };\n    }, rootRef);\n    return () => media.revert();\n  }, { scope: rootRef, dependencies: [year, month, pickerYear !== null, ready, density, weekStartsOn], revertOnUpdate: true });\n\n  useEffect(() => {\n    if (!ready || !rootRef.current) return;\n    const reduced = window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n    const fills = rootRef.current.querySelectorAll(\"[data-calendar-fill]\");\n    const fillTween = fills.length ? gsap.to(fills, {\n      scaleX: (index: number) => Math.max(0, Math.min(1, day / count * 4 - index)),\n      duration: reduced ? 0 : 0.35,\n      ease: \"power3.out\",\n      overwrite: true,\n    }) : undefined;\n    const highlight = rootRef.current.querySelector<HTMLElement>(\"[data-calendar-highlight]\");\n    const selected = rootRef.current.querySelector<HTMLElement>(\"[data-selected-day]\");\n    let highlightTween: gsap.core.Tween | undefined;\n    let observer: ResizeObserver | undefined;\n    if (highlight && selected) {\n      const first = previousHighlight.current !== highlight;\n      previousHighlight.current = highlight;\n      const position = { x: selected.offsetLeft, y: selected.offsetTop, opacity: 1 };\n      highlightTween = gsap.to(highlight, {\n        ...position,\n        duration: reduced || first ? 0 : 0.3,\n        ease: \"power3.out\",\n        overwrite: true,\n      });\n      let width = highlight.parentElement!.clientWidth;\n      observer = new ResizeObserver(() => {\n        const nextWidth = highlight.parentElement!.clientWidth;\n        if (nextWidth === width) return;\n        width = nextWidth;\n        highlightTween?.kill();\n        gsap.set(highlight, { x: selected.offsetLeft, y: selected.offsetTop, opacity: 1 });\n      });\n      observer.observe(highlight.parentElement!);\n    }\n    return () => {\n      fillTween?.kill();\n      highlightTween?.kill();\n      observer?.disconnect();\n    };\n  }, [day, count, year, month, pickerYear, ready, density, weekStartsOn, showProgress]);\n\n  useEffect(() => {\n    if (!keyboardFocus.current) return;\n    rootRef.current?.querySelector<HTMLButtonElement>(\"[data-selected-day]\")?.focus();\n    keyboardFocus.current = false;\n  }, [year, month, day]);\n\n  useGSAP(() => {\n    const popup = popupRef.current;\n    const anchor = popupAnchor.current;\n    const root = rootRef.current;\n    if (!popupOpen || !popup || !anchor || !root) return;\n    const media = gsap.matchMedia();\n    const position = () => {\n      const card = root.getBoundingClientRect();\n      const cell = anchor.getBoundingClientRect();\n      const width = popup.offsetWidth;\n      const height = popup.offsetHeight;\n      const x = Math.max(12, Math.min(cell.left - card.left + cell.width / 2 - width / 2, root.clientWidth - width - 12));\n      const below = cell.bottom - card.top + 6;\n      const y = Math.max(12, below + height < root.clientHeight - 12 ? below : cell.top - card.top - height - 6);\n      gsap.set(popup, { left: x, top: y });\n    };\n    position();\n    const observer = new ResizeObserver(position);\n    observer.observe(root);\n    observer.observe(popup);\n    media.add(\"(prefers-reduced-motion: no-preference)\", () => {\n      gsap.from(popup, { y: 5, opacity: 0, duration: 0.18, ease: \"power3.out\", clearProps: \"transform,opacity\" });\n    }, rootRef);\n    return () => { observer.disconnect(); media.revert(); };\n  }, { scope: rootRef, dependencies: [popupDay, popupOpen, year, month, density, weekStartsOn], revertOnUpdate: true });\n\n  useEffect(() => () => { closePopupTween.current?.kill(); }, []);\n\n  useEffect(() => {\n    if (!popupOpen) return;\n    const dismiss = (event: PointerEvent) => {\n      const target = event.target as Node;\n      if (!popupRef.current?.contains(target) && !popupAnchor.current?.contains(target)) setPopupDay(null);\n    };\n    document.addEventListener(\"pointerdown\", dismiss, true);\n    return () => document.removeEventListener(\"pointerdown\", dismiss, true);\n  }, [popupOpen]);\n\n  function showPopup(number: number, anchor: HTMLButtonElement) {\n    closePopupTween.current?.kill();\n    popupAnchor.current = anchor;\n    setPopupDay(number);\n  }\n\n  function schedulePopupClose() {\n    closePopupTween.current?.kill();\n    closePopupTween.current = gsap.delayedCall(0.15, () => {\n      if (!popupRef.current?.contains(document.activeElement)) setPopupDay(null);\n    });\n  }\n\n  function pick(next: Date) {\n    closePopupTween.current?.kill();\n    setPopupDay(null);\n    if (value === undefined) setInternalDate(next);\n    onChange?.(new Date(next));\n  }\n\n  function onDayKey(event: KeyboardEvent<HTMLButtonElement>, number: number) {\n    const current = new Date(year, month, number);\n    const steps: Record<string, number> = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 };\n    let next: Date;\n    if (event.key in steps) {\n      next = new Date(year, month, number + steps[event.key]);\n    } else if (event.key === \"Home\") next = new Date(year, month, 1);\n    else if (event.key === \"End\") next = new Date(year, month, count);\n    else if (event.key === \"PageUp\" || event.key === \"PageDown\") {\n      next = inMonth(current, year, month + (event.key === \"PageUp\" ? -1 : 1));\n    } else return;\n    event.preventDefault();\n    keyboardFocus.current = true;\n    pick(next);\n  }\n\n  return (\n    <Dialog.Root open={eventFormDate !== null} onOpenChange={(open) => { setEventFormDate(open ? dateKey : null); setPopupDay(null); }}>\n    <div\n      ref={rootRef}\n      data-slot=\"calendar-card\"\n      role=\"group\"\n      aria-label=\"Date picker\"\n      aria-busy={!ready}\n      onKeyDown={(event) => { if (event.key === \"Escape\" && popupOpen) { event.stopPropagation(); popupAnchor.current?.focus(); setPopupDay(null); } }}\n      style={{\n        \"--calendar-accent\": accentColor,\n        \"--calendar-cell-mobile\": density === \"compact\" ? \"36px\" : \"40px\",\n        \"--calendar-cell-desktop\": density === \"compact\" ? \"36px\" : \"44px\",\n      } as CSSProperties}\n      className={cn(\n        \"@container relative w-full max-w-[44rem] rounded-3xl border border-white/10 bg-[#191919] p-4 text-zinc-100 shadow-[0_24px_48px_-16px_#000,0_1px_1px_0_#ffffff18,inset_0_1px_0_0_#ffffff0a] sm:rounded-[2rem] sm:p-8\",\n        className,\n      )}\n    >\n      <div className={cn(\"@min-[480px]:grid @min-[480px]:grid-cols-[184px_minmax(0,1fr)] @min-[480px]:gap-x-8\", !ready && \"invisible\")}>\n        <div className=\"@min-[480px]:col-start-1 @min-[480px]:row-start-1\">\n          <div className={cn(\"flex items-start justify-between gap-3 @min-[480px]:flex-col-reverse @min-[480px]:gap-5\", onEventAdd && \"flex-col-reverse gap-4\")}>\n            <div className=\"flex min-w-0 items-center gap-4 @min-[480px]:block\">\n              <span className=\"mb-3 hidden text-[10px] font-medium uppercase tracking-[0.18em] text-zinc-400 @min-[480px]:block\">Selected date</span>\n              <div data-calendar-day-number className=\"relative h-14 w-[2ch] shrink-0 overflow-hidden text-5xl font-medium leading-none tracking-[-0.07em] tabular-nums @min-[480px]:h-20 @min-[480px]:text-7xl\" aria-hidden=\"true\">\n                <span data-calendar-day-out className=\"absolute inset-x-0 top-0 opacity-0\" />\n                <span data-calendar-day className=\"block\">{day}</span>\n              </div>\n              <button ref={titleRef} type=\"button\" aria-expanded={pickerYear !== null} aria-controls={panelId} aria-label=\"Choose month and year\" onClick={() => setPickerYear(pickerYear === null ? year : null)} className={cn(buttonClass, \"grid min-h-11 flex-1 grid-cols-[1fr_auto] items-center gap-x-2 px-2 py-1 text-left text-base hover:bg-white/5 @min-[480px]:-ml-2 @min-[480px]:flex @min-[480px]:flex-none @min-[480px]:min-h-0 @min-[480px]:gap-1\")}>\n                <span className=\"col-start-1 font-medium @min-[480px]:w-24 @min-[480px]:shrink-0\">{months[month]}</span>\n                <span className=\"col-start-1 row-start-2 text-sm text-zinc-400 @min-[480px]:w-[4ch] @min-[480px]:shrink-0 @min-[480px]:text-base @min-[480px]:tabular-nums\">{year}</span>\n                <ChevronDown aria-hidden=\"true\" size={14} className={cn(\"col-start-2 row-span-2 row-start-1 text-zinc-400\", pickerYear !== null && \"rotate-180\")} />\n              </button>\n            </div>\n            <div className={cn(\"mt-1 flex items-center gap-3\", onEventAdd && \"w-full justify-between\")}>\n              <button type=\"button\" aria-label=\"Go to today\" title=\"Go to today\" onClick={() => { const now = new Date(); pick(new Date(now.getFullYear(), now.getMonth(), now.getDate())); setPickerYear(null); }} className={cn(buttonClass, \"group h-10 w-10 shrink-0 @min-[480px]:h-12 @min-[480px]:w-12 overflow-hidden border border-white/5 bg-[#202020] shadow-[0_3px_8px_#0004] hover:bg-[#292929]\")}>\n                <span aria-hidden=\"true\" className=\"block h-3 bg-[var(--calendar-accent)] shadow-[0_2px_8px_color-mix(in_srgb,var(--calendar-accent)_20%,transparent)]\" />\n                <span aria-hidden=\"true\" className=\"grid grid-cols-7 gap-0.5 p-2\">\n                  {Array.from({ length: 21 }, (_, index) => <span key={index} className={cn(\"h-0.5 rounded-full bg-zinc-600\", index === (day - 1) % 21 && \"bg-[var(--calendar-accent)]\")} />)}\n                </span>\n              </button>\n              {onEventAdd && (\n                <Dialog.Trigger asChild>\n                  <button type=\"button\" className={cn(buttonClass, \"flex min-h-10 shrink-0 items-center gap-2 border border-white/10 bg-white/5 px-3 py-2 text-xs font-medium hover:bg-white/10\")}><Plus size={14} aria-hidden=\"true\" />Add event</button>\n                </Dialog.Trigger>\n              )}\n            </div>\n          </div>\n\n          {showProgress && <div className=\"my-4 flex gap-1.5 @min-[480px]:my-6\" aria-hidden=\"true\">\n            {Array.from({ length: 4 }, (_, index) => (\n              <span key={index} className=\"h-1 flex-1 overflow-hidden rounded-full bg-white/7\">\n                <span data-calendar-fill className=\"block h-full origin-left rounded-full bg-[var(--calendar-accent)]\" style={{ transform: \"scaleX(0)\" }} />\n              </span>\n            ))}\n          </div>}\n        </div>\n\n        <div id={panelId} data-calendar-panel className=\"min-h-72 @min-[480px]:col-start-2 @min-[480px]:row-span-2 @min-[480px]:row-start-1 @min-[480px]:min-h-86 @min-[480px]:border-l @min-[480px]:border-white/5 @min-[480px]:pl-8\" onKeyDown={(event) => { if (event.key === \"Escape\" && pickerYear !== null) { setPickerYear(null); titleRef.current?.focus(); } }}>\n          {pickerYear !== null ? (\n            <div>\n              <div className=\"mb-5 flex items-center justify-between\">\n                <button type=\"button\" aria-label=\"Previous year\" onClick={() => setPickerYear(pickerYear - 1)} className={cn(buttonClass, \"flex h-11 w-11 items-center justify-center rounded-full bg-white/5 @min-[480px]:h-9 @min-[480px]:w-9 hover:bg-white/10\")}><ChevronLeft aria-hidden=\"true\" size={16} /></button>\n                <span aria-live=\"polite\" className=\"inline-flex h-7 items-center justify-center text-lg font-semibold leading-7 tabular-nums\">\n                  <span className=\"sr-only\">{pickerYear}</span>\n                  {String(pickerYear).split(\"\").map((digit, index, digits) => (\n                    <span key={digits.length - index} data-calendar-year-digit={digits.length - index - 1} aria-hidden=\"true\" className=\"relative block h-7 w-[1ch] overflow-hidden text-center\">\n                      <span data-calendar-year-out className=\"absolute inset-x-0 top-0 opacity-0\" />\n                      <span data-calendar-year className=\"block\">{digit}</span>\n                    </span>\n                  ))}\n                </span>\n                <button type=\"button\" aria-label=\"Next year\" onClick={() => setPickerYear(pickerYear + 1)} className={cn(buttonClass, \"flex h-11 w-11 items-center justify-center rounded-full bg-white/5 @min-[480px]:h-9 @min-[480px]:w-9 hover:bg-white/10\")}><ChevronRight aria-hidden=\"true\" size={16} /></button>\n              </div>\n              <div role=\"group\" aria-label={`Months in ${pickerYear}`} className=\"grid grid-cols-3 gap-x-4 gap-y-3 @min-[480px]:gap-y-5\">\n                {months.map((name, index) => (\n                  <button key={name} type=\"button\" aria-label={`${name} ${pickerYear}`} aria-pressed={index === month && pickerYear === year} onClick={() => { pick(inMonth(date, pickerYear, index)); setPickerYear(null); titleRef.current?.focus(); }} className={cn(buttonClass, \"h-11 text-sm font-medium @min-[480px]:h-11 @min-[480px]:text-base\", index === month && pickerYear === year ? \"bg-[var(--calendar-accent)] text-zinc-950 shadow-[0_2px_12px_color-mix(in_srgb,var(--calendar-accent)_16%,transparent)]\" : \"text-zinc-300 hover:bg-white/5 hover:text-white\")}>\n                    {name.slice(0, 3)}\n                  </button>\n                ))}\n              </div>\n            </div>\n          ) : (\n            <div role=\"group\" aria-label={`${months[month]} ${year}`}>\n              <div aria-hidden=\"true\" className=\"mb-3 grid grid-cols-7 @min-[480px]:mb-5\">\n                {week.map((name) => <span key={name} className=\"text-center text-xs font-medium text-zinc-400\">{name[0]}</span>)}\n              </div>\n              <div data-calendar-grid className=\"relative grid grid-cols-7 gap-y-1 @min-[480px]:gap-y-2\">\n                <span data-calendar-highlight aria-hidden=\"true\" className=\"pointer-events-none absolute left-0 top-0 h-[var(--calendar-cell-mobile)] w-[calc(100%/7)] rounded-lg bg-[var(--calendar-accent)] opacity-0 shadow-[0_2px_12px_color-mix(in_srgb,var(--calendar-accent)_16%,transparent)] @min-[480px]:h-[var(--calendar-cell-desktop)]\" />\n                <span data-calendar-hover aria-hidden=\"true\" className=\"pointer-events-none absolute left-0 top-0 z-[1] h-[var(--calendar-cell-mobile)] w-[calc(100%/7)] rounded-lg border border-white/10 bg-white/5 opacity-0 @min-[480px]:h-[var(--calendar-cell-desktop)]\" />\n                {Array.from({ length: offset }, (_, index) => <span key={`empty-${index}`} aria-hidden=\"true\" />)}\n                {Array.from({ length: count }, (_, index) => {\n                  const number = index + 1;\n                  const dayEvents = eventsByDay.get(number) ?? [];\n                  return (\n                    <button\n                      key={number}\n                      type=\"button\"\n                      data-calendar-date=\"\"\n                      data-selected-day={number === day ? \"\" : undefined}\n                      aria-label={`${months[month]} ${number}, ${year}${dayEvents.length ? `, ${dayEvents.length} ${dayEvents.length === 1 ? \"event\" : \"events\"}` : \"\"}`}\n                      aria-pressed={number === day}\n                      tabIndex={number === day ? 0 : -1}\n                      aria-haspopup={showEvents && dayEvents.length > 0 ? \"dialog\" : undefined}\n                      aria-expanded={showEvents && dayEvents.length > 0 ? popupOpen && popupDay === number : undefined}\n                      aria-controls={popupOpen && popupDay === number ? popupId : undefined}\n                      onPointerEnter={(event) => { if (event.pointerType !== \"touch\" && !eventFormDate) showPopup(number, event.currentTarget); }}\n                      onPointerLeave={schedulePopupClose}\n                      onFocus={(event) => { if (event.currentTarget.matches(\":focus-visible\") && !eventFormDate) showPopup(number, event.currentTarget); }}\n                      onBlur={(event) => { if (!popupRef.current?.contains(event.relatedTarget)) schedulePopupClose(); }}\n                      onClick={(event) => { pick(new Date(year, month, number)); showPopup(number, event.currentTarget); }}\n                      onKeyDown={(event) => onDayKey(event, number)}\n                      className={cn(\n                        buttonClass,\n                        \"relative z-10 flex h-[var(--calendar-cell-mobile)] flex-col items-center justify-center rounded-lg text-base font-medium tabular-nums @min-[480px]:h-[var(--calendar-cell-desktop)]\",\n                        number === day\n                          ? \"text-zinc-950\"\n                          : \"text-zinc-200 hover:text-white\",\n                      )}\n                    >\n                      <span className=\"leading-none\">{number}</span>\n                      {events.length > 0 && (\n                        <span aria-hidden=\"true\" className=\"mt-1 flex h-[3px] items-center justify-center gap-[3px]\">\n                          {dayEvents.slice(0, 3).map((event) => <span key={event.id} className=\"size-[3px] rounded-full\" style={{ backgroundColor: number === day ? \"#09090b\" : event.color ?? accentColor }} />)}\n                        </span>\n                      )}\n                    </button>\n                  );\n                })}\n              </div>\n            </div>\n          )}\n        </div>\n        {popupOpen && (\n          <div ref={popupRef} id={popupId} role=\"dialog\" aria-label={`Events for ${months[month]} ${popupDay}, ${year}`} onPointerEnter={() => closePopupTween.current?.kill()} onPointerLeave={schedulePopupClose} onFocus={() => closePopupTween.current?.kill()} onBlur={(event) => { if (!event.currentTarget.contains(event.relatedTarget)) schedulePopupClose(); }} className=\"absolute z-30 w-[min(17rem,calc(100%_-_24px))] rounded-2xl border border-white/10 bg-[#242424] p-4 shadow-[0_12px_40px_#0009]\">\n            <div className=\"mb-3 flex items-center justify-between gap-3\">\n              <span className=\"text-xs font-medium\">{months[month].slice(0, 3)} {popupDay} · {popupEvents.length} {popupEvents.length === 1 ? \"event\" : \"events\"}</span>\n              <button type=\"button\" aria-label=\"Close event popup\" onClick={() => { popupAnchor.current?.focus(); setPopupDay(null); }} className={cn(buttonClass, \"flex size-6 items-center justify-center text-zinc-400 hover:bg-white/10\")}><X size={13} aria-hidden=\"true\" /></button>\n            </div>\n            <ul className=\"max-h-40 space-y-2 overflow-y-auto\">\n              {popupEvents.map((event) => (\n                <li key={event.id}>\n                  <EventTag type={onEventClick ? \"button\" : undefined} onClick={onEventClick ? () => onEventClick(event) : undefined} className={cn(\"flex w-full items-start gap-2 rounded-xl px-2 py-2 text-left\", onEventClick && [buttonClass, \"bg-white/4 hover:bg-white/8\"])}>\n                    <span aria-hidden=\"true\" className=\"mt-1 size-1.5 shrink-0 rounded-full\" style={{ backgroundColor: event.color ?? accentColor }} />\n                    <span className=\"min-w-0 flex-1\">\n                      <span className=\"block break-words text-xs font-medium\">{event.title}</span>\n                      <span className=\"mt-1 block text-[10px] text-zinc-400\">{event.time ?? \"All day\"}{event.assignee && ` · ${event.assignee}`}</span>\n                    </span>\n                  </EventTag>\n                </li>\n              ))}\n            </ul>\n            {onEventAdd && <button type=\"button\" onClick={() => { setEventFormDate(`${monthKey}-${String(popupDay).padStart(2, \"0\")}`); setPopupDay(null); }} className={cn(buttonClass, \"mt-3 flex w-full items-center justify-center gap-2 border-t border-white/10 pt-3 text-xs text-zinc-300\")}><Plus size={13} aria-hidden=\"true\" />Add event to this day</button>}\n          </div>\n        )}\n        <div className=\"mt-4 flex items-center justify-between border-t border-white/5 pt-3 @min-[480px]:col-start-1 @min-[480px]:row-start-2 @min-[480px]:self-end\">\n          <button type=\"button\" aria-label=\"Previous month\" onClick={() => { pick(inMonth(date, year, month - 1)); setPickerYear(null); }} className={cn(buttonClass, \"flex h-11 w-11 items-center justify-center text-zinc-400 @min-[480px]:h-8 @min-[480px]:w-8 hover:bg-white/5 hover:text-white\")}><ChevronLeft aria-hidden=\"true\" size={15} /></button>\n          <span className=\"flex-1 text-center text-xs font-medium text-zinc-400\">{weekdays[(date.getDay() + 6) % 7].slice(0, 3)}, {months[month].slice(0, 3)} {day}</span>\n          <button type=\"button\" aria-label=\"Advance one month\" onClick={() => { pick(inMonth(date, year, month + 1)); setPickerYear(null); }} className={cn(buttonClass, \"flex h-11 w-11 items-center justify-center text-zinc-400 @min-[480px]:h-8 @min-[480px]:w-8 hover:bg-white/5 hover:text-white\")}><ChevronRight aria-hidden=\"true\" size={15} /></button>\n        </div>\n        <span role=\"status\" className=\"sr-only\">Selected date: {selectedLabel}{events.length > 0 && `, ${selectedEvents.length} ${selectedEvents.length === 1 ? \"event\" : \"events\"}`}</span>\n      </div>\n    </div>\n    <Dialog.Portal>\n      <Dialog.Overlay className=\"fixed inset-0 z-50 bg-black/60 backdrop-blur-sm\" />\n      <Dialog.Content onOpenAutoFocus={(event) => { event.preventDefault(); eventTitleRef.current?.focus(); }} style={{ \"--calendar-accent\": accentColor } as CSSProperties} className=\"fixed left-1/2 top-1/2 z-50 max-h-[90dvh] w-[calc(100%_-_2rem)] max-w-sm -translate-x-1/2 -translate-y-1/2 overflow-y-auto rounded-3xl border border-white/10 bg-[#202020] p-6 text-zinc-100 shadow-2xl\">\n        <Dialog.Title className=\"text-lg font-semibold\">Add an event</Dialog.Title>\n        <Dialog.Description className=\"mt-1 text-xs text-zinc-400\">Choose a date and optionally assign it to someone.</Dialog.Description>\n        <Dialog.Close aria-label=\"Close add event\" className={cn(buttonClass, \"absolute right-4 top-4 flex size-8 items-center justify-center hover:bg-white/5\")}><X size={16} aria-hidden=\"true\" /></Dialog.Close>\n        <form className=\"mt-5 space-y-4\" onSubmit={(event) => {\n          event.preventDefault();\n          const form = event.currentTarget;\n          const fields = new FormData(form);\n          const title = String(fields.get(\"title\") ?? \"\").trim();\n          const titleInput = form.elements.namedItem(\"title\") as HTMLInputElement;\n          if (!title) { titleInput.setCustomValidity(\"Enter an event title.\"); titleInput.reportValidity(); return; }\n          const eventDate = String(fields.get(\"date\"));\n          onEventAdd?.({ id: `${panelId}-event-${++eventCounter.current}`, date: eventDate, title, time: String(fields.get(\"time\") ?? \"\") || undefined, assignee: String(fields.get(\"assignee\") ?? \"\").trim() || undefined, color: String(fields.get(\"color\")) });\n          pick(new Date(`${eventDate}T00:00:00`));\n          setEventFormDate(null);\n        }}>\n          <label className=\"block space-y-1.5 text-xs text-zinc-300\">Title<input ref={eventTitleRef} name=\"title\" required maxLength={120} placeholder=\"Design review\" onInput={(event) => event.currentTarget.setCustomValidity(\"\")} className={cn(buttonClass, \"block w-full cursor-text rounded-lg border border-white/10 bg-white/5 px-3 py-2.5 text-base text-white sm:text-sm\")} /></label>\n          <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n            <label className=\"block space-y-1.5 text-xs text-zinc-300\">Date<input name=\"date\" type=\"date\" required defaultValue={eventFormDate ?? dateKey} className={cn(buttonClass, \"block w-full min-w-0 rounded-lg border border-white/10 bg-white/5 px-2 py-2.5 text-base [color-scheme:dark] sm:text-xs\")} /></label>\n            <label className=\"block space-y-1.5 text-xs text-zinc-300\">Time<input name=\"time\" type=\"time\" className={cn(buttonClass, \"block w-full min-w-0 rounded-lg border border-white/10 bg-white/5 px-2 py-2.5 text-base [color-scheme:dark] sm:text-xs\")} /></label>\n          </div>\n          <label className=\"block space-y-1.5 text-xs text-zinc-300\">Assign to <span className=\"text-zinc-500\">(optional)</span><input name=\"assignee\" maxLength={80} placeholder=\"Name or team\" className={cn(buttonClass, \"block w-full cursor-text rounded-lg border border-white/10 bg-white/5 px-3 py-2.5 text-base text-white sm:text-sm\")} /></label>\n          <label className=\"flex items-center justify-between text-xs text-zinc-300\">Event color<input name=\"color\" type=\"color\" defaultValue={accentColor.startsWith(\"#\") && accentColor.length === 7 ? accentColor : \"#ff675c\"} className=\"h-8 w-10 cursor-pointer rounded border-0 bg-transparent\" /></label>\n          <div className=\"flex justify-end gap-2 pt-2\">\n            <Dialog.Close className={cn(buttonClass, \"px-4 py-2 text-xs text-zinc-400 hover:bg-white/5\")}>Cancel</Dialog.Close>\n            <button type=\"submit\" className={cn(buttonClass, \"bg-[var(--calendar-accent)] px-4 py-2 text-xs font-semibold text-zinc-950\")}>Save event</button>\n          </div>\n        </form>\n      </Dialog.Content>\n    </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\nexport default CalendarCard;\n",
      "type": "registry:ui",
      "target": "@ui/calendar-card.tsx"
    }
  ]
}
