{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "calendar-year",
  "type": "registry:block",
  "title": "Calendar Year",
  "description": "A calendar year component.",
  "dependencies": ["dayjs", "@radix-ui/react-slot", "class-variance-authority", "@radix-ui/react-tooltip"],
  "registryDependencies": ["button", "tooltip"],
  "files": [
    {
      "path": "registry/abui/ui/calendar-year.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"@/components/ui/tooltip\"\nimport { cn } from \"@/lib/utils\"\nimport dayjs from \"dayjs\"\nimport timezone from \"dayjs/plugin/timezone\"\nimport utc from \"dayjs/plugin/utc\"\ndayjs.extend(timezone)\ndayjs.extend(utc)\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * State of a calendar day\n */\nexport type DayState = \"blocked\" | \"disabled\"\n\n/**\n * Day data structure\n */\nexport interface CalendarDay {\n  date: string // YYYY-MM-DD format\n  state?: DayState\n  disabled?: boolean\n  tooltip?: string\n}\n\n/**\n * Month structure with rows of days\n */\nexport interface CalendarMonth {\n  name: string\n  monthIndex: number\n  weekdayLabels: string[]\n  rows: (CalendarDay | null)[][]\n}\n\n// ============================================================================\n// Variants\n// ============================================================================\n\nconst calendarYearDayVariants = cva(\n  \"cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full text-sm font-semibold transition-all disabled:pointer-events-none disabled:opacity-50 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive relative h-9 w-9 p-0\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-foreground text-background dark:text-background hover:bg-foreground/90 border border-foreground\",\n        \"default-success\":\n          \"bg-green-500 text-background hover:bg-green-600 hover:border-green-600 dark:hover:bg-green-400 dark:hover:border-green-400 border border-green-500\",\n        accent: \"bg-accent text-background dark:text-background hover:bg-accent/90 border border-accent\",\n        destructive:\n          \"bg-destructive hover:!bg-destructive text-background focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 border border-destructive\",\n        outline:\n          \"border bg-background shadow-xs hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50\",\n        \"outline-destructive\": \"border border-destructive bg-transparent shadow-xs text-destructive bg-transparent\",\n        \"outline-accent\": \"border border-accent bg-transparent shadow-xs text-accent bg-transparent\",\n        \"outline-success\": \"border border-green-500 bg-transparent shadow-xs text-green-500 bg-transparent\",\n        secondary: \"bg-secondary text-secondary-foreground hover:bg-secondary/80 border border-secondary\",\n        ghost:\n          \"bg-transparent text-muted-foreground hover:bg-secondary hover:text-foreground border border-transparent\",\n      },\n    },\n    defaultVariants: {\n      variant: \"outline\",\n    },\n  },\n)\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Generate calendar structure for a year\n * Uses dayjs locale for month and weekday names\n *\n * @param year - Year to generate calendar for\n * @param timezone - Timezone for date calculations (default: \"Europe/Lisbon\")\n * @returns Array of months with rows of days (dates only, no state)\n */\nexport function generateYearCalendar(year: number, timezone: string = \"Europe/Lisbon\"): CalendarMonth[] {\n  const months: CalendarMonth[] = []\n\n  // Get weekday labels (Monday to Sunday) using dayjs locale\n  const weekdayLabels: string[] = []\n  const weekDaysIndexes = [1, 2, 3, 4, 5, 6, 0] // Mon-Sun\n  for (const dayIndex of weekDaysIndexes) {\n    // Create a date that falls on this weekday\n    const sampleDate = dayjs().tz(timezone).day(dayIndex)\n    const dayName = sampleDate.format(\"dddd\")\n    // Capitalize first letter\n    weekdayLabels.push(dayName.charAt(0).toUpperCase() + dayName.slice(1))\n  }\n\n  for (let monthIndex = 0; monthIndex < 12; monthIndex++) {\n    const firstDayOfMonth = dayjs().tz(timezone).year(year).month(monthIndex).date(1)\n    const monthName = firstDayOfMonth.format(\"MMMM\")\n    // Capitalize first letter\n    const capitalizedMonthName = monthName.charAt(0).toUpperCase() + monthName.slice(1)\n    const daysInMonth = firstDayOfMonth.daysInMonth()\n    const monthDays: CalendarDay[] = []\n\n    // Generate all days for this month (just dates, no business logic)\n    for (let day = 1; day <= daysInMonth; day++) {\n      const date = dayjs().tz(timezone).year(year).month(monthIndex).date(day).format(\"YYYY-MM-DD\")\n\n      monthDays.push({\n        date,\n        state: undefined,\n        disabled: false,\n        tooltip: undefined,\n      })\n    }\n\n    // Organize days into rows (weeks)\n    const rows: (CalendarDay | null)[][] = []\n    const monthDaysClone = [...monthDays]\n\n    while (monthDaysClone.length > 0) {\n      const newRow: (CalendarDay | null)[] = []\n      for (const dayIndex of weekDaysIndexes) {\n        const testingDay = monthDaysClone[0]\n        if (testingDay && dayjs(testingDay.date).tz(timezone).day() === dayIndex) {\n          newRow.push(testingDay)\n          monthDaysClone.shift()\n        } else {\n          newRow.push(null)\n        }\n      }\n      rows.push(newRow)\n    }\n\n    months.push({\n      name: capitalizedMonthName,\n      monthIndex,\n      weekdayLabels,\n      rows,\n    })\n  }\n\n  return months\n}\n\n/**\n * Check if a date is today\n */\nconst isDateToday = (date: string, timezone: string): boolean => {\n  return dayjs(date).tz(timezone).isSame(dayjs().tz(timezone), \"day\")\n}\n\n// ============================================================================\n// Root Component (Container)\n// ============================================================================\n\nfunction CalendarYear({ className, children, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div data-slot=\"calendar-year\" className={cn(\"flex h-full w-full flex-col\", className)} {...props}>\n      {children}\n    </div>\n  )\n}\n\n// ============================================================================\n// Content Component (Scrollable Container)\n// ============================================================================\n\ninterface CalendarYearContentProps extends React.ComponentProps<\"div\"> {\n  scrollToCurrentMonth?: boolean\n  timezone?: string\n}\n\nfunction CalendarYearContent({\n  scrollToCurrentMonth = false,\n  timezone = \"Europe/Lisbon\",\n  className,\n  children,\n  ...props\n}: CalendarYearContentProps) {\n  const scrollContainerRef = React.useRef<HTMLDivElement>(null)\n  const [hasScrolled, setHasScrolled] = React.useState(false)\n\n  React.useEffect(() => {\n    if (scrollToCurrentMonth && !hasScrolled && scrollContainerRef.current) {\n      const currentMonth = dayjs().tz(timezone).month()\n      const monthClass = `calendar-month-${currentMonth}`\n      const element = scrollContainerRef.current.querySelector(`.${monthClass}`)\n\n      if (element) {\n        element.scrollIntoView({ behavior: \"instant\", block: \"start\" })\n        setHasScrolled(true)\n      }\n    }\n  }, [scrollToCurrentMonth, hasScrolled, timezone])\n\n  return (\n    <div\n      ref={scrollContainerRef}\n      data-slot=\"calendar-year-content\"\n      className={cn(\n        \"flex flex-col gap-12 overflow-y-auto\",\n        \"transition-opacity duration-300\",\n        scrollToCurrentMonth && !hasScrolled && \"opacity-0\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n\n// ============================================================================\n// Month Component\n// ============================================================================\n\ninterface CalendarYearMonthProps extends React.ComponentProps<\"div\"> {\n  name: string\n  monthIndex: number\n}\n\nfunction CalendarYearMonth({ name, monthIndex, className, children, ...props }: CalendarYearMonthProps) {\n  return (\n    <div\n      data-slot=\"calendar-month\"\n      className={cn(`calendar-month-${monthIndex} flex flex-col gap-3`, className)}\n      {...props}\n    >\n      <h3 className=\"text-lg font-semibold\">{name}</h3>\n      {children}\n    </div>\n  )\n}\n\n// ============================================================================\n// Weekday Header Component\n// ============================================================================\n\ninterface CalendarYearWeekdayHeaderProps extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n  labels: string[]\n}\n\nfunction CalendarYearWeekdayHeader({ labels, className, ...props }: CalendarYearWeekdayHeaderProps) {\n  return (\n    <div\n      data-slot=\"calendar-weekday-header\"\n      className={cn(\"grid grid-cols-7 gap-2 border-b pb-2 mb-2 border-border\", className)}\n      {...props}\n    >\n      {labels.map((label, i) => (\n        <p key={i} className=\"text-sm text-muted-foreground text-left truncate\">\n          {label}\n        </p>\n      ))}\n    </div>\n  )\n}\n\n// ============================================================================\n// Week Component\n// ============================================================================\n\nfunction CalendarYearWeek({ className, children, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div data-slot=\"calendar-week\" className={cn(\"grid grid-cols-7 gap-2\", className)} {...props}>\n      {children}\n    </div>\n  )\n}\n\n// ============================================================================\n// Day Component\n// ============================================================================\n\ninterface CalendarYearDayProps extends Omit<React.ComponentProps<\"button\">, \"children\"> {\n  date: string\n  state?: DayState\n  variant?: VariantProps<typeof calendarYearDayVariants>[\"variant\"]\n  disabled?: boolean\n  tooltip?: string\n  timezone?: string\n  asChild?: boolean\n}\n\nconst CalendarYearDay = React.forwardRef<HTMLButtonElement, CalendarYearDayProps>(\n  (\n    {\n      date,\n      state,\n      variant = \"outline\",\n      disabled = false,\n      tooltip,\n      timezone = \"Europe/Lisbon\",\n      className,\n      asChild = false,\n      ...props\n    },\n    ref,\n  ) => {\n    const dayNumber = dayjs(date).tz(timezone).format(\"DD\")\n    const isToday = isDateToday(date, timezone)\n    const Comp = asChild ? Slot : \"button\"\n\n    // Handle blocked state (not disabled but not clickable)\n    const isBlocked = state === \"blocked\"\n    const isDisabled = disabled || state === \"disabled\"\n\n    const buttonContent = (\n      <Comp\n        ref={ref}\n        data-slot=\"calendar-day\"\n        data-state={state}\n        data-today={isToday}\n        data-disabled={isDisabled}\n        data-blocked={isBlocked}\n        type=\"button\"\n        disabled={isDisabled}\n        className={cn(\n          calendarYearDayVariants({ variant, className }),\n\n          // Blocked state (red) - not-allowed cursor, no hover effects\n          // \"data-[blocked=true]:!bg-background data-[blocked=true]:!text-destructive data-[blocked=true]:!border-destructive\",\n          \"data-[blocked=true]:!cursor-not-allowed\",\n\n          // Today indicator\n          \"data-[today=true]:ring-4 data-[today=true]:ring-accent data-[today=true]:ring-offset-[0.5px]\",\n        )}\n        {...props}\n      >\n        {dayNumber}\n      </Comp>\n    )\n\n    if (!isDisabled && !isBlocked && tooltip) {\n      return (\n        <TooltipProvider delayDuration={200}>\n          <Tooltip>\n            <TooltipTrigger asChild>{buttonContent}</TooltipTrigger>\n            <TooltipContent>\n              <p>{tooltip}</p>\n            </TooltipContent>\n          </Tooltip>\n        </TooltipProvider>\n      )\n    }\n\n    return buttonContent\n  },\n)\nCalendarYearDay.displayName = \"CalendarYearDay\"\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nexport {\n  CalendarYear,\n  CalendarYearContent,\n  CalendarYearMonth,\n  CalendarYearWeekdayHeader,\n  CalendarYearWeek,\n  CalendarYearDay,\n  calendarYearDayVariants,\n}\n",
      "type": "registry:component"
    },
    {
      "path": "components/ui/tooltip.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction TooltipProvider({ delayDuration = 0, ...props }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {\n  return <TooltipPrimitive.Provider data-slot=\"tooltip-provider\" delayDuration={delayDuration} {...props} />\n}\n\nfunction Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) {\n  return (\n    <TooltipProvider>\n      <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\n    </TooltipProvider>\n  )\n}\n\nfunction TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />\n}\n\nfunction TooltipContent({\n  className,\n  sideOffset = 0,\n  children,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Content>) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Content\n        data-slot=\"tooltip-content\"\n        sideOffset={sideOffset}\n        className={cn(\n          \"bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance\",\n          className,\n        )}\n        {...props}\n      >\n        {children}\n        <TooltipPrimitive.Arrow className=\"bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]\" />\n      </TooltipPrimitive.Content>\n    </TooltipPrimitive.Portal>\n  )\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }\n",
      "type": "registry:ui",
      "target": ""
    }
  ]
}
