{"name":"Kibo UI Registry","homepage":"https://www.kibo-ui.com/","items":[{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"announcement","type":"registry:ui","title":"announcement","description":"A compound badge designed to display an announcement.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":[],"devDependencies":[],"registryDependencies":["badge"],"files":[{"type":"registry:ui","path":"index.tsx","content":"import type { ComponentProps, HTMLAttributes } from \"react\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { cn } from \"@/lib/utils\";\n\nexport type AnnouncementProps = ComponentProps<typeof Badge> & {\n  themed?: boolean;\n};\n\nexport const Announcement = ({\n  variant = \"outline\",\n  themed = false,\n  className,\n  ...props\n}: AnnouncementProps) => (\n  <Badge\n    className={cn(\n      \"group max-w-full gap-2 rounded-full bg-background px-3 py-0.5 font-medium shadow-sm transition-all\",\n      \"hover:shadow-md\",\n      themed && \"announcement-themed border-foreground/5\",\n      className\n    )}\n    variant={variant}\n    {...props}\n  />\n);\n\nexport type AnnouncementTagProps = HTMLAttributes<HTMLDivElement>;\n\nexport const AnnouncementTag = ({\n  className,\n  ...props\n}: AnnouncementTagProps) => (\n  <div\n    className={cn(\n      \"-ml-2.5 shrink-0 truncate rounded-full bg-foreground/5 px-2.5 py-1 text-xs\",\n      \"group-[.announcement-themed]:bg-background/60\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type AnnouncementTitleProps = HTMLAttributes<HTMLDivElement>;\n\nexport const AnnouncementTitle = ({\n  className,\n  ...props\n}: AnnouncementTitleProps) => (\n  <div\n    className={cn(\"flex items-center gap-1 truncate py-1\", className)}\n    {...props}\n  />\n);\n","target":"components/kibo-ui/announcement/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"avatar-stack","type":"registry:ui","title":"avatar-stack","description":"Avatar Stack is a component that allows you to stack and overlap avatars.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":[],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"import { Children, type ReactNode } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type AvatarStackProps = {\n  children: ReactNode;\n  className?: string;\n  animate?: boolean;\n  size?: number;\n};\n\nexport const AvatarStack = ({\n  children,\n  className,\n  animate = false,\n  size = 40,\n  ...props\n}: AvatarStackProps) => (\n  <div\n    className={cn(\n      \"-space-x-1 flex items-center\",\n      animate && \"hover:space-x-0 [&>*]:transition-all\",\n      className\n    )}\n    {...props}\n  >\n    {Children.map(children, (child, index) => {\n      if (!child) {\n        return null;\n      }\n\n      return (\n        <div\n          className={cn(\n            \"size-full shrink-0 overflow-hidden rounded-full\",\n            '[&_[data-slot=\"avatar\"]]:size-full',\n            className\n          )}\n          style={{\n            width: size,\n            height: size,\n            maskImage: index\n              ? `radial-gradient(circle ${size / 2}px at -${size / 4 + size / 10}px 50%, transparent 99%, white 100%)`\n              : \"\",\n          }}\n        >\n          {child}\n        </div>\n      );\n    })}\n  </div>\n);\n","target":"components/kibo-ui/avatar-stack/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"banner","type":"registry:ui","title":"banner","description":"A banner is a full-width component that can be used to show a message and action to the user.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@radix-ui/react-use-controllable-state","lucide-react"],"devDependencies":[],"registryDependencies":["button"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport { type LucideIcon, XIcon } from \"lucide-react\";\nimport {\n  type ComponentProps,\n  createContext,\n  type HTMLAttributes,\n  type MouseEventHandler,\n  useContext,\n} from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\ntype BannerContextProps = {\n  show: boolean;\n  setShow: (show: boolean) => void;\n};\n\nexport const BannerContext = createContext<BannerContextProps>({\n  show: true,\n  setShow: () => {},\n});\n\nexport type BannerProps = HTMLAttributes<HTMLDivElement> & {\n  visible?: boolean;\n  defaultVisible?: boolean;\n  onClose?: () => void;\n  inset?: boolean;\n};\n\nexport const Banner = ({\n  children,\n  visible,\n  defaultVisible = true,\n  onClose,\n  className,\n  inset = false,\n  ...props\n}: BannerProps) => {\n  const [show, setShow] = useControllableState({\n    defaultProp: defaultVisible,\n    prop: visible,\n    onChange: onClose,\n  });\n\n  if (!show) {\n    return null;\n  }\n\n  return (\n    <BannerContext.Provider value={{ show, setShow }}>\n      <div\n        className={cn(\n          \"flex w-full items-center justify-between gap-2 bg-primary px-4 py-2 text-primary-foreground\",\n          inset && \"rounded-lg\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    </BannerContext.Provider>\n  );\n};\n\nexport type BannerIconProps = HTMLAttributes<HTMLDivElement> & {\n  icon: LucideIcon;\n};\n\nexport const BannerIcon = ({\n  icon: Icon,\n  className,\n  ...props\n}: BannerIconProps) => (\n  <div\n    className={cn(\n      \"rounded-full border border-background/20 bg-background/10 p-1 shadow-sm\",\n      className\n    )}\n    {...props}\n  >\n    <Icon size={16} />\n  </div>\n);\n\nexport type BannerTitleProps = HTMLAttributes<HTMLParagraphElement>;\n\nexport const BannerTitle = ({ className, ...props }: BannerTitleProps) => (\n  <p className={cn(\"flex-1 text-sm\", className)} {...props} />\n);\n\nexport type BannerActionProps = ComponentProps<typeof Button>;\n\nexport const BannerAction = ({\n  variant = \"outline\",\n  size = \"sm\",\n  className,\n  ...props\n}: BannerActionProps) => (\n  <Button\n    className={cn(\n      \"shrink-0 bg-transparent hover:bg-background/10 hover:text-background\",\n      className\n    )}\n    size={size}\n    variant={variant}\n    {...props}\n  />\n);\n\nexport type BannerCloseProps = ComponentProps<typeof Button>;\n\nexport const BannerClose = ({\n  variant = \"ghost\",\n  size = \"icon\",\n  onClick,\n  className,\n  ...props\n}: BannerCloseProps) => {\n  const { setShow } = useContext(BannerContext);\n\n  const handleClick: MouseEventHandler<HTMLButtonElement> = (e) => {\n    setShow(false);\n    onClick?.(e);\n  };\n\n  return (\n    <Button\n      className={cn(\n        \"shrink-0 bg-transparent hover:bg-background/10 hover:text-background\",\n        className\n      )}\n      onClick={handleClick}\n      size={size}\n      variant={variant}\n      {...props}\n    >\n      <XIcon size={18} />\n    </Button>\n  );\n};\n","target":"components/kibo-ui/banner/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"calendar","type":"registry:ui","title":"calendar","description":"The calendar view displays features on a grid calendar. Specifically it shows the end date of each feature, and groups features by day.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["date-fns","jotai","lucide-react"],"devDependencies":[],"registryDependencies":["button","command","popover"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { getDay, getDaysInMonth, isSameDay } from \"date-fns\";\nimport { atom, useAtom } from \"jotai\";\nimport {\n  Check,\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  ChevronsUpDown,\n} from \"lucide-react\";\nimport {\n  createContext,\n  memo,\n  type ReactNode,\n  useCallback,\n  useContext,\n  useMemo,\n  useState,\n} from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n} from \"@/components/ui/command\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { cn } from \"@/lib/utils\";\n\nexport type CalendarState = {\n  month: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11;\n  year: number;\n};\n\nconst monthAtom = atom<CalendarState[\"month\"]>(\n  new Date().getMonth() as CalendarState[\"month\"]\n);\nconst yearAtom = atom<CalendarState[\"year\"]>(new Date().getFullYear());\n\nexport const useCalendarMonth = () => useAtom(monthAtom);\nexport const useCalendarYear = () => useAtom(yearAtom);\n\ntype CalendarContextProps = {\n  locale: Intl.LocalesArgument;\n  startDay: number;\n};\n\nconst CalendarContext = createContext<CalendarContextProps>({\n  locale: \"en-US\",\n  startDay: 0,\n});\n\nexport type Status = {\n  id: string;\n  name: string;\n  color: string;\n};\n\nexport type Feature = {\n  id: string;\n  name: string;\n  startAt: Date;\n  endAt: Date;\n  status: Status;\n};\n\ntype ComboboxProps = {\n  value: string;\n  setValue: (value: string) => void;\n  data: {\n    value: string;\n    label: string;\n  }[];\n  labels: {\n    button: string;\n    empty: string;\n    search: string;\n  };\n  className?: string;\n};\n\nexport const monthsForLocale = (\n  localeName: Intl.LocalesArgument,\n  monthFormat: Intl.DateTimeFormatOptions[\"month\"] = \"long\"\n) => {\n  const format = new Intl.DateTimeFormat(localeName, { month: monthFormat })\n    .format;\n\n  return [...new Array(12).keys()].map((m) =>\n    format(new Date(Date.UTC(2021, m, 2)))\n  );\n};\n\nexport const daysForLocale = (\n  locale: Intl.LocalesArgument,\n  startDay: number\n) => {\n  const weekdays: string[] = [];\n  const baseDate = new Date(2024, 0, startDay);\n\n  for (let i = 0; i < 7; i++) {\n    weekdays.push(\n      new Intl.DateTimeFormat(locale, { weekday: \"short\" }).format(baseDate)\n    );\n    baseDate.setDate(baseDate.getDate() + 1);\n  }\n\n  return weekdays;\n};\n\nconst Combobox = ({\n  value,\n  setValue,\n  data,\n  labels,\n  className,\n}: ComboboxProps) => {\n  const [open, setOpen] = useState(false);\n\n  return (\n    <Popover onOpenChange={setOpen} open={open}>\n      <PopoverTrigger asChild>\n        <Button\n          aria-expanded={open}\n          className={cn(\"w-40 justify-between capitalize\", className)}\n          variant=\"outline\"\n        >\n          {value\n            ? data.find((item) => item.value === value)?.label\n            : labels.button}\n          <ChevronsUpDown className=\"ml-2 h-4 w-4 shrink-0 opacity-50\" />\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent className=\"w-40 p-0\">\n        <Command\n          filter={(value, search) => {\n            const label = data.find((item) => item.value === value)?.label;\n\n            return label?.toLowerCase().includes(search.toLowerCase()) ? 1 : 0;\n          }}\n        >\n          <CommandInput placeholder={labels.search} />\n          <CommandList>\n            <CommandEmpty>{labels.empty}</CommandEmpty>\n            <CommandGroup>\n              {data.map((item) => (\n                <CommandItem\n                  className=\"capitalize\"\n                  key={item.value}\n                  onSelect={(currentValue) => {\n                    setValue(currentValue === value ? \"\" : currentValue);\n                    setOpen(false);\n                  }}\n                  value={item.value}\n                >\n                  <Check\n                    className={cn(\n                      \"mr-2 h-4 w-4\",\n                      value === item.value ? \"opacity-100\" : \"opacity-0\"\n                    )}\n                  />\n                  {item.label}\n                </CommandItem>\n              ))}\n            </CommandGroup>\n          </CommandList>\n        </Command>\n      </PopoverContent>\n    </Popover>\n  );\n};\n\ntype OutOfBoundsDayProps = {\n  day: number;\n};\n\nconst OutOfBoundsDay = ({ day }: OutOfBoundsDayProps) => (\n  <div className=\"relative h-full w-full bg-secondary p-1 text-muted-foreground text-xs\">\n    {day}\n  </div>\n);\n\nexport type CalendarBodyProps = {\n  features: Feature[];\n  children: (props: { feature: Feature }) => ReactNode;\n};\n\nexport const CalendarBody = ({ features, children }: CalendarBodyProps) => {\n  const [month] = useCalendarMonth();\n  const [year] = useCalendarYear();\n  const { startDay } = useContext(CalendarContext);\n\n  // Memoize expensive date calculations\n  const currentMonthDate = useMemo(\n    () => new Date(year, month, 1),\n    [year, month]\n  );\n  const daysInMonth = useMemo(\n    () => getDaysInMonth(currentMonthDate),\n    [currentMonthDate]\n  );\n  const firstDay = useMemo(\n    () => (getDay(currentMonthDate) - startDay + 7) % 7,\n    [currentMonthDate, startDay]\n  );\n\n  // Memoize previous month calculations\n  const prevMonthData = useMemo(() => {\n    const prevMonth = month === 0 ? 11 : month - 1;\n    const prevMonthYear = month === 0 ? year - 1 : year;\n    const prevMonthDays = getDaysInMonth(new Date(prevMonthYear, prevMonth, 1));\n    const prevMonthDaysArray = Array.from(\n      { length: prevMonthDays },\n      (_, i) => i + 1\n    );\n    return { prevMonthDays, prevMonthDaysArray };\n  }, [month, year]);\n\n  // Memoize next month calculations\n  const nextMonthData = useMemo(() => {\n    const nextMonth = month === 11 ? 0 : month + 1;\n    const nextMonthYear = month === 11 ? year + 1 : year;\n    const nextMonthDays = getDaysInMonth(new Date(nextMonthYear, nextMonth, 1));\n    const nextMonthDaysArray = Array.from(\n      { length: nextMonthDays },\n      (_, i) => i + 1\n    );\n    return { nextMonthDaysArray };\n  }, [month, year]);\n\n  // Memoize features filtering by day to avoid recalculating on every render\n  const featuresByDay = useMemo(() => {\n    const result: { [day: number]: Feature[] } = {};\n    for (let day = 1; day <= daysInMonth; day++) {\n      result[day] = features.filter((feature) => {\n        return isSameDay(new Date(feature.endAt), new Date(year, month, day));\n      });\n    }\n    return result;\n  }, [features, daysInMonth, year, month]);\n\n  const days: ReactNode[] = [];\n\n  for (let i = 0; i < firstDay; i++) {\n    const day =\n      prevMonthData.prevMonthDaysArray[\n        prevMonthData.prevMonthDays - firstDay + i\n      ];\n\n    if (day) {\n      days.push(<OutOfBoundsDay day={day} key={`prev-${i}`} />);\n    }\n  }\n\n  for (let day = 1; day <= daysInMonth; day++) {\n    const featuresForDay = featuresByDay[day] || [];\n\n    days.push(\n      <div\n        className=\"relative flex h-full w-full flex-col gap-1 p-1 text-muted-foreground text-xs\"\n        key={day}\n      >\n        {day}\n        <div>\n          {featuresForDay.slice(0, 3).map((feature) => children({ feature }))}\n        </div>\n        {featuresForDay.length > 3 && (\n          <span className=\"block text-muted-foreground text-xs\">\n            +{featuresForDay.length - 3} more\n          </span>\n        )}\n      </div>\n    );\n  }\n\n  const remainingDays = 7 - ((firstDay + daysInMonth) % 7);\n  if (remainingDays < 7) {\n    for (let i = 0; i < remainingDays; i++) {\n      const day = nextMonthData.nextMonthDaysArray[i];\n\n      if (day) {\n        days.push(<OutOfBoundsDay day={day} key={`next-${i}`} />);\n      }\n    }\n  }\n\n  return (\n    <div className=\"grid flex-grow grid-cols-7\">\n      {days.map((day, index) => (\n        <div\n          className={cn(\n            \"relative aspect-square overflow-hidden border-t border-r\",\n            index % 7 === 6 && \"border-r-0\"\n          )}\n          key={index}\n        >\n          {day}\n        </div>\n      ))}\n    </div>\n  );\n};\n\nexport type CalendarDatePickerProps = {\n  className?: string;\n  children: ReactNode;\n};\n\nexport const CalendarDatePicker = ({\n  className,\n  children,\n}: CalendarDatePickerProps) => (\n  <div className={cn(\"flex items-center gap-1\", className)}>{children}</div>\n);\n\nexport type CalendarMonthPickerProps = {\n  className?: string;\n};\n\nexport const CalendarMonthPicker = ({\n  className,\n}: CalendarMonthPickerProps) => {\n  const [month, setMonth] = useCalendarMonth();\n  const { locale } = useContext(CalendarContext);\n\n  // Memoize month data to avoid recalculating date formatting\n  const monthData = useMemo(() => {\n    return monthsForLocale(locale).map((month, index) => ({\n      value: index.toString(),\n      label: month,\n    }));\n  }, [locale]);\n\n  return (\n    <Combobox\n      className={className}\n      data={monthData}\n      labels={{\n        button: \"Select month\",\n        empty: \"No month found\",\n        search: \"Search month\",\n      }}\n      setValue={(value) =>\n        setMonth(Number.parseInt(value, 10) as CalendarState[\"month\"])\n      }\n      value={month.toString()}\n    />\n  );\n};\n\nexport type CalendarYearPickerProps = {\n  className?: string;\n  start: number;\n  end: number;\n};\n\nexport const CalendarYearPicker = ({\n  className,\n  start,\n  end,\n}: CalendarYearPickerProps) => {\n  const [year, setYear] = useCalendarYear();\n\n  return (\n    <Combobox\n      className={className}\n      data={Array.from({ length: end - start + 1 }, (_, i) => ({\n        value: (start + i).toString(),\n        label: (start + i).toString(),\n      }))}\n      labels={{\n        button: \"Select year\",\n        empty: \"No year found\",\n        search: \"Search year\",\n      }}\n      setValue={(value) => setYear(Number.parseInt(value, 10))}\n      value={year.toString()}\n    />\n  );\n};\n\nexport type CalendarDatePaginationProps = {\n  className?: string;\n};\n\nexport const CalendarDatePagination = ({\n  className,\n}: CalendarDatePaginationProps) => {\n  const [month, setMonth] = useCalendarMonth();\n  const [year, setYear] = useCalendarYear();\n\n  const handlePreviousMonth = useCallback(() => {\n    if (month === 0) {\n      setMonth(11);\n      setYear(year - 1);\n    } else {\n      setMonth((month - 1) as CalendarState[\"month\"]);\n    }\n  }, [month, year, setMonth, setYear]);\n\n  const handleNextMonth = useCallback(() => {\n    if (month === 11) {\n      setMonth(0);\n      setYear(year + 1);\n    } else {\n      setMonth((month + 1) as CalendarState[\"month\"]);\n    }\n  }, [month, year, setMonth, setYear]);\n\n  return (\n    <div className={cn(\"flex items-center gap-2\", className)}>\n      <Button onClick={handlePreviousMonth} size=\"icon\" variant=\"ghost\">\n        <ChevronLeftIcon size={16} />\n      </Button>\n      <Button onClick={handleNextMonth} size=\"icon\" variant=\"ghost\">\n        <ChevronRightIcon size={16} />\n      </Button>\n    </div>\n  );\n};\n\nexport type CalendarDateProps = {\n  children: ReactNode;\n};\n\nexport const CalendarDate = ({ children }: CalendarDateProps) => (\n  <div className=\"flex items-center justify-between p-3\">{children}</div>\n);\n\nexport type CalendarHeaderProps = {\n  className?: string;\n};\n\nexport const CalendarHeader = ({ className }: CalendarHeaderProps) => {\n  const { locale, startDay } = useContext(CalendarContext);\n\n  // Memoize days data to avoid recalculating date formatting\n  const daysData = useMemo(() => {\n    return daysForLocale(locale, startDay);\n  }, [locale, startDay]);\n\n  return (\n    <div className={cn(\"grid flex-grow grid-cols-7\", className)}>\n      {daysData.map((day) => (\n        <div className=\"p-3 text-right text-muted-foreground text-xs\" key={day}>\n          {day}\n        </div>\n      ))}\n    </div>\n  );\n};\n\nexport type CalendarItemProps = {\n  feature: Feature;\n  className?: string;\n};\n\nexport const CalendarItem = memo(\n  ({ feature, className }: CalendarItemProps) => (\n    <div className={cn(\"flex items-center gap-2\", className)}>\n      <div\n        className=\"h-2 w-2 shrink-0 rounded-full\"\n        style={{\n          backgroundColor: feature.status.color,\n        }}\n      />\n      <span className=\"truncate\">{feature.name}</span>\n    </div>\n  )\n);\n\nCalendarItem.displayName = \"CalendarItem\";\n\nexport type CalendarProviderProps = {\n  locale?: Intl.LocalesArgument;\n  startDay?: number;\n  children: ReactNode;\n  className?: string;\n};\n\nexport const CalendarProvider = ({\n  locale = \"en-US\",\n  startDay = 0,\n  children,\n  className,\n}: CalendarProviderProps) => (\n  <CalendarContext.Provider value={{ locale, startDay }}>\n    <div className={cn(\"relative flex flex-col\", className)}>{children}</div>\n  </CalendarContext.Provider>\n);\n","target":"components/kibo-ui/calendar/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"choicebox","type":"registry:ui","title":"choicebox","description":"Choiceboxes are a great way to show radio or checkbox options with a card style.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["lucide-react","radix-ui"],"devDependencies":[],"registryDependencies":["field"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport {\n  RadioGroup,\n  RadioGroupItem,\n} from \"@repo/shadcn-ui/components/ui/radio-group\";\nimport { cn } from \"@repo/shadcn-ui/lib/utils\";\nimport {\n  type ComponentProps,\n  createContext,\n  type HTMLAttributes,\n  useContext,\n} from \"react\";\nimport {\n  Field,\n  FieldContent,\n  FieldDescription,\n  FieldLabel,\n  FieldTitle,\n} from \"@/components/ui/field\";\n\nexport type ChoiceboxProps = ComponentProps<typeof RadioGroup>;\n\nexport const Choicebox = ({ className, ...props }: ChoiceboxProps) => (\n  <RadioGroup className={cn(\"w-full\", className)} {...props} />\n);\n\ntype ChoiceboxItemContextValue = {\n  value: ChoiceboxItemProps[\"value\"];\n  id?: ChoiceboxItemProps[\"id\"];\n};\n\nconst ChoiceboxItemContext = createContext<ChoiceboxItemContextValue | null>(\n  null\n);\n\nconst useChoiceboxItemContext = () => {\n  const context = useContext(ChoiceboxItemContext);\n\n  if (!context) {\n    throw new Error(\n      \"useChoiceboxItemContext must be used within a ChoiceboxItem\"\n    );\n  }\n\n  return context;\n};\n\nexport type ChoiceboxItemProps = ComponentProps<typeof RadioGroupItem>;\n\nexport const ChoiceboxItem = ({\n  className,\n  children,\n  value,\n  id,\n}: ChoiceboxItemProps) => (\n  <ChoiceboxItemContext.Provider value={{ value, id }}>\n    <FieldLabel htmlFor={id}>\n      <Field className={className} orientation=\"horizontal\">\n        {children}\n      </Field>\n    </FieldLabel>\n  </ChoiceboxItemContext.Provider>\n);\n\nexport type ChoiceboxItemHeaderProps = ComponentProps<typeof FieldContent>;\n\nexport const ChoiceboxItemHeader = ({\n  className,\n  ...props\n}: ChoiceboxItemHeaderProps) => (\n  <FieldContent className={className} {...props} />\n);\n\nexport type ChoiceboxItemTitleProps = ComponentProps<typeof FieldTitle>;\n\nexport const ChoiceboxItemTitle = ({\n  className,\n  ...props\n}: ChoiceboxItemTitleProps) => <FieldTitle className={className} {...props} />;\n\nexport type ChoiceboxItemSubtitleProps = HTMLAttributes<HTMLSpanElement>;\n\nexport const ChoiceboxItemSubtitle = ({\n  className,\n  ...props\n}: ChoiceboxItemSubtitleProps) => (\n  <span\n    className={cn(\"font-normal text-muted-foreground text-xs\", className)}\n    {...props}\n  />\n);\n\nexport type ChoiceboxItemDescriptionProps = ComponentProps<\n  typeof FieldDescription\n>;\n\nexport const ChoiceboxItemDescription = ({\n  className,\n  ...props\n}: ChoiceboxItemDescriptionProps) => (\n  <FieldDescription className={className} {...props} />\n);\n\nexport type ChoiceboxIndicatorProps = Partial<\n  ComponentProps<typeof RadioGroupItem>\n>;\n\nexport const ChoiceboxIndicator = (props: ChoiceboxIndicatorProps) => {\n  const context = useChoiceboxItemContext();\n\n  return <RadioGroupItem {...props} value={context.value} />;\n};\n","target":"components/kibo-ui/choicebox/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"code-block","type":"registry:ui","title":"code-block","description":"Provides syntax highlighting, line numbers, and copy to clipboard functionality for code blocks.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@radix-ui/react-use-controllable-state","@shikijs/transformers","lucide-react","react-icons","shiki"],"devDependencies":[],"registryDependencies":["button","select"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport {\n  transformerNotationDiff,\n  transformerNotationErrorLevel,\n  transformerNotationFocus,\n  transformerNotationHighlight,\n  transformerNotationWordHighlight,\n} from \"@shikijs/transformers\";\nimport { CheckIcon, CopyIcon } from \"lucide-react\";\nimport type {\n  ComponentProps,\n  HTMLAttributes,\n  ReactElement,\n  ReactNode,\n} from \"react\";\nimport {\n  cloneElement,\n  createContext,\n  useContext,\n  useEffect,\n  useState,\n} from \"react\";\nimport type { IconType } from \"react-icons\";\nimport {\n  SiAstro,\n  SiBiome,\n  SiBower,\n  SiBun,\n  SiC,\n  SiCircleci,\n  SiCoffeescript,\n  SiCplusplus,\n  SiCss3,\n  SiCssmodules,\n  SiDart,\n  SiDocker,\n  SiDocusaurus,\n  SiDotenv,\n  SiEditorconfig,\n  SiEslint,\n  SiGatsby,\n  SiGitignoredotio,\n  SiGnubash,\n  SiGo,\n  SiGraphql,\n  SiGrunt,\n  SiGulp,\n  SiHandlebarsdotjs,\n  SiHtml5,\n  SiJavascript,\n  SiJest,\n  SiJson,\n  SiLess,\n  SiMarkdown,\n  SiMdx,\n  SiMintlify,\n  SiMocha,\n  SiMysql,\n  SiNextdotjs,\n  SiPerl,\n  SiPhp,\n  SiPostcss,\n  SiPrettier,\n  SiPrisma,\n  SiPug,\n  SiPython,\n  SiR,\n  SiReact,\n  SiReadme,\n  SiRedis,\n  SiRemix,\n  SiRive,\n  SiRollupdotjs,\n  SiRuby,\n  SiSanity,\n  SiSass,\n  SiScala,\n  SiSentry,\n  SiShadcnui,\n  SiStorybook,\n  SiStylelint,\n  SiSublimetext,\n  SiSvelte,\n  SiSvg,\n  SiSwift,\n  SiTailwindcss,\n  SiToml,\n  SiTypescript,\n  SiVercel,\n  SiVite,\n  SiVuedotjs,\n  SiWebassembly,\n} from \"react-icons/si\";\nimport {\n  type BundledLanguage,\n  type CodeOptionsMultipleThemes,\n  codeToHtml,\n} from \"shiki\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { cn } from \"@/lib/utils\";\n\nexport type { BundledLanguage } from \"shiki\";\n\nconst filenameIconMap = {\n  \".env\": SiDotenv,\n  \"*.astro\": SiAstro,\n  \"biome.json\": SiBiome,\n  \".bowerrc\": SiBower,\n  \"bun.lockb\": SiBun,\n  \"*.c\": SiC,\n  \"*.cpp\": SiCplusplus,\n  \".circleci/config.yml\": SiCircleci,\n  \"*.coffee\": SiCoffeescript,\n  \"*.module.css\": SiCssmodules,\n  \"*.css\": SiCss3,\n  \"*.dart\": SiDart,\n  Dockerfile: SiDocker,\n  \"docusaurus.config.js\": SiDocusaurus,\n  \".editorconfig\": SiEditorconfig,\n  \".eslintrc\": SiEslint,\n  \"eslint.config.*\": SiEslint,\n  \"gatsby-config.*\": SiGatsby,\n  \".gitignore\": SiGitignoredotio,\n  \"*.go\": SiGo,\n  \"*.graphql\": SiGraphql,\n  \"*.sh\": SiGnubash,\n  \"Gruntfile.*\": SiGrunt,\n  \"gulpfile.*\": SiGulp,\n  \"*.hbs\": SiHandlebarsdotjs,\n  \"*.html\": SiHtml5,\n  \"*.js\": SiJavascript,\n  \"*.json\": SiJson,\n  \"*.test.js\": SiJest,\n  \"*.less\": SiLess,\n  \"*.md\": SiMarkdown,\n  \"*.mdx\": SiMdx,\n  \"mintlify.json\": SiMintlify,\n  \"mocha.opts\": SiMocha,\n  \"*.mustache\": SiHandlebarsdotjs,\n  \"*.sql\": SiMysql,\n  \"next.config.*\": SiNextdotjs,\n  \"*.pl\": SiPerl,\n  \"*.php\": SiPhp,\n  \"postcss.config.*\": SiPostcss,\n  \"prettier.config.*\": SiPrettier,\n  \"*.prisma\": SiPrisma,\n  \"*.pug\": SiPug,\n  \"*.py\": SiPython,\n  \"*.r\": SiR,\n  \"*.rb\": SiRuby,\n  \"*.jsx\": SiReact,\n  \"*.tsx\": SiReact,\n  \"readme.md\": SiReadme,\n  \"*.rdb\": SiRedis,\n  \"remix.config.*\": SiRemix,\n  \"*.riv\": SiRive,\n  \"rollup.config.*\": SiRollupdotjs,\n  \"sanity.config.*\": SiSanity,\n  \"*.sass\": SiSass,\n  \"*.scss\": SiSass,\n  \"*.sc\": SiScala,\n  \"*.scala\": SiScala,\n  \"sentry.client.config.*\": SiSentry,\n  \"components.json\": SiShadcnui,\n  \"storybook.config.*\": SiStorybook,\n  \"stylelint.config.*\": SiStylelint,\n  \".sublime-settings\": SiSublimetext,\n  \"*.svelte\": SiSvelte,\n  \"*.svg\": SiSvg,\n  \"*.swift\": SiSwift,\n  \"tailwind.config.*\": SiTailwindcss,\n  \"*.toml\": SiToml,\n  \"*.ts\": SiTypescript,\n  \"vercel.json\": SiVercel,\n  \"vite.config.*\": SiVite,\n  \"*.vue\": SiVuedotjs,\n  \"*.wasm\": SiWebassembly,\n};\n\nconst lineNumberClassNames = cn(\n  \"[&_code]:[counter-reset:line]\",\n  \"[&_code]:[counter-increment:line_0]\",\n  \"[&_.line]:before:content-[counter(line)]\",\n  \"[&_.line]:before:inline-block\",\n  \"[&_.line]:before:[counter-increment:line]\",\n  \"[&_.line]:before:w-4\",\n  \"[&_.line]:before:mr-4\",\n  \"[&_.line]:before:text-[13px]\",\n  \"[&_.line]:before:text-right\",\n  \"[&_.line]:before:text-muted-foreground/50\",\n  \"[&_.line]:before:font-mono\",\n  \"[&_.line]:before:select-none\"\n);\n\nconst darkModeClassNames = cn(\n  \"dark:[&_.shiki]:!text-[var(--shiki-dark)]\",\n  // \"dark:[&_.shiki]:!bg-[var(--shiki-dark-bg)]\",\n  \"dark:[&_.shiki]:![font-style:var(--shiki-dark-font-style)]\",\n  \"dark:[&_.shiki]:![font-weight:var(--shiki-dark-font-weight)]\",\n  \"dark:[&_.shiki]:![text-decoration:var(--shiki-dark-text-decoration)]\",\n  \"dark:[&_.shiki_span]:!text-[var(--shiki-dark)]\",\n  \"dark:[&_.shiki_span]:![font-style:var(--shiki-dark-font-style)]\",\n  \"dark:[&_.shiki_span]:![font-weight:var(--shiki-dark-font-weight)]\",\n  \"dark:[&_.shiki_span]:![text-decoration:var(--shiki-dark-text-decoration)]\"\n);\n\nconst lineHighlightClassNames = cn(\n  \"[&_.line.highlighted]:bg-blue-50\",\n  \"[&_.line.highlighted]:after:bg-blue-500\",\n  \"[&_.line.highlighted]:after:absolute\",\n  \"[&_.line.highlighted]:after:left-0\",\n  \"[&_.line.highlighted]:after:top-0\",\n  \"[&_.line.highlighted]:after:bottom-0\",\n  \"[&_.line.highlighted]:after:w-0.5\",\n  \"dark:[&_.line.highlighted]:!bg-blue-500/10\"\n);\n\nconst lineDiffClassNames = cn(\n  \"[&_.line.diff]:after:absolute\",\n  \"[&_.line.diff]:after:left-0\",\n  \"[&_.line.diff]:after:top-0\",\n  \"[&_.line.diff]:after:bottom-0\",\n  \"[&_.line.diff]:after:w-0.5\",\n  \"[&_.line.diff.add]:bg-emerald-50\",\n  \"[&_.line.diff.add]:after:bg-emerald-500\",\n  \"[&_.line.diff.remove]:bg-rose-50\",\n  \"[&_.line.diff.remove]:after:bg-rose-500\",\n  \"dark:[&_.line.diff.add]:!bg-emerald-500/10\",\n  \"dark:[&_.line.diff.remove]:!bg-rose-500/10\"\n);\n\nconst lineFocusedClassNames = cn(\n  \"[&_code:has(.focused)_.line]:blur-[2px]\",\n  \"[&_code:has(.focused)_.line.focused]:blur-none\"\n);\n\nconst wordHighlightClassNames = cn(\n  \"[&_.highlighted-word]:bg-blue-50\",\n  \"dark:[&_.highlighted-word]:!bg-blue-500/10\"\n);\n\nconst codeBlockClassName = cn(\n  \"mt-0 bg-background text-sm\",\n  \"[&_pre]:py-4\",\n  // \"[&_.shiki]:!bg-[var(--shiki-bg)]\",\n  \"[&_.shiki]:!bg-transparent\",\n  \"[&_code]:w-full\",\n  \"[&_code]:grid\",\n  \"[&_code]:overflow-x-auto\",\n  \"[&_code]:bg-transparent\",\n  \"[&_.line]:px-4\",\n  \"[&_.line]:w-full\",\n  \"[&_.line]:relative\"\n);\n\nconst highlight = (\n  html: string,\n  language?: BundledLanguage,\n  themes?: CodeOptionsMultipleThemes[\"themes\"]\n) =>\n  codeToHtml(html, {\n    lang: language ?? \"typescript\",\n    themes: themes ?? {\n      light: \"github-light\",\n      dark: \"github-dark-default\",\n    },\n    transformers: [\n      transformerNotationDiff({\n        matchAlgorithm: \"v3\",\n      }),\n      transformerNotationHighlight({\n        matchAlgorithm: \"v3\",\n      }),\n      transformerNotationWordHighlight({\n        matchAlgorithm: \"v3\",\n      }),\n      transformerNotationFocus({\n        matchAlgorithm: \"v3\",\n      }),\n      transformerNotationErrorLevel({\n        matchAlgorithm: \"v3\",\n      }),\n    ],\n  });\n\ntype CodeBlockData = {\n  language: string;\n  filename: string;\n  code: string;\n};\n\ntype CodeBlockContextType = {\n  value: string | undefined;\n  onValueChange: ((value: string) => void) | undefined;\n  data: CodeBlockData[];\n};\n\nconst CodeBlockContext = createContext<CodeBlockContextType>({\n  value: undefined,\n  onValueChange: undefined,\n  data: [],\n});\n\nexport type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {\n  defaultValue?: string;\n  value?: string;\n  onValueChange?: (value: string) => void;\n  data: CodeBlockData[];\n};\n\nexport const CodeBlock = ({\n  value: controlledValue,\n  onValueChange: controlledOnValueChange,\n  defaultValue,\n  className,\n  data,\n  ...props\n}: CodeBlockProps) => {\n  const [value, onValueChange] = useControllableState({\n    defaultProp: defaultValue ?? \"\",\n    prop: controlledValue,\n    onChange: controlledOnValueChange,\n  });\n\n  return (\n    <CodeBlockContext.Provider value={{ value, onValueChange, data }}>\n      <div\n        className={cn(\"size-full overflow-hidden rounded-md border\", className)}\n        {...props}\n      />\n    </CodeBlockContext.Provider>\n  );\n};\n\nexport type CodeBlockHeaderProps = HTMLAttributes<HTMLDivElement>;\n\nexport const CodeBlockHeader = ({\n  className,\n  ...props\n}: CodeBlockHeaderProps) => (\n  <div\n    className={cn(\n      \"flex flex-row items-center border-b bg-secondary p-1\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type CodeBlockFilesProps = Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> & {\n  children: (item: CodeBlockData) => ReactNode;\n};\n\nexport const CodeBlockFiles = ({\n  className,\n  children,\n  ...props\n}: CodeBlockFilesProps) => {\n  const { data } = useContext(CodeBlockContext);\n\n  return (\n    <div\n      className={cn(\"flex grow flex-row items-center gap-2\", className)}\n      {...props}\n    >\n      {data.map(children)}\n    </div>\n  );\n};\n\nexport type CodeBlockFilenameProps = HTMLAttributes<HTMLDivElement> & {\n  icon?: IconType;\n  value?: string;\n};\n\nexport const CodeBlockFilename = ({\n  className,\n  icon,\n  value,\n  children,\n  ...props\n}: CodeBlockFilenameProps) => {\n  const { value: activeValue } = useContext(CodeBlockContext);\n  const defaultIcon = Object.entries(filenameIconMap).find(([pattern]) => {\n    const regex = new RegExp(\n      `^${pattern.replace(/\\\\/g, \"\\\\\\\\\").replace(/\\./g, \"\\\\.\").replace(/\\*/g, \".*\")}$`\n    );\n    return regex.test(children as string);\n  })?.[1];\n  const Icon = icon ?? defaultIcon;\n\n  if (value !== activeValue) {\n    return null;\n  }\n\n  return (\n    <div\n      className=\"flex items-center gap-2 bg-secondary px-4 py-1.5 text-muted-foreground text-xs\"\n      {...props}\n    >\n      {Icon && <Icon className=\"h-4 w-4 shrink-0\" />}\n      <span className=\"flex-1 truncate\">{children}</span>\n    </div>\n  );\n};\n\nexport type CodeBlockSelectProps = ComponentProps<typeof Select>;\n\nexport const CodeBlockSelect = (props: CodeBlockSelectProps) => {\n  const { value, onValueChange } = useContext(CodeBlockContext);\n\n  return <Select onValueChange={onValueChange} value={value} {...props} />;\n};\n\nexport type CodeBlockSelectTriggerProps = ComponentProps<typeof SelectTrigger>;\n\nexport const CodeBlockSelectTrigger = ({\n  className,\n  ...props\n}: CodeBlockSelectTriggerProps) => (\n  <SelectTrigger\n    className={cn(\n      \"w-fit border-none text-muted-foreground text-xs shadow-none\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type CodeBlockSelectValueProps = ComponentProps<typeof SelectValue>;\n\nexport const CodeBlockSelectValue = (props: CodeBlockSelectValueProps) => (\n  <SelectValue {...props} />\n);\n\nexport type CodeBlockSelectContentProps = Omit<\n  ComponentProps<typeof SelectContent>,\n  \"children\"\n> & {\n  children: (item: CodeBlockData) => ReactNode;\n};\n\nexport const CodeBlockSelectContent = ({\n  children,\n  ...props\n}: CodeBlockSelectContentProps) => {\n  const { data } = useContext(CodeBlockContext);\n\n  return <SelectContent {...props}>{data.map(children)}</SelectContent>;\n};\n\nexport type CodeBlockSelectItemProps = ComponentProps<typeof SelectItem>;\n\nexport const CodeBlockSelectItem = ({\n  className,\n  ...props\n}: CodeBlockSelectItemProps) => (\n  <SelectItem className={cn(\"text-sm\", className)} {...props} />\n);\n\nexport type CodeBlockCopyButtonProps = ComponentProps<typeof Button> & {\n  onCopy?: () => void;\n  onError?: (error: Error) => void;\n  timeout?: number;\n};\n\nexport const CodeBlockCopyButton = ({\n  asChild,\n  onCopy,\n  onError,\n  timeout = 2000,\n  children,\n  className,\n  ...props\n}: CodeBlockCopyButtonProps) => {\n  const [isCopied, setIsCopied] = useState(false);\n  const { data, value } = useContext(CodeBlockContext);\n  const code = data.find((item) => item.language === value)?.code;\n\n  const copyToClipboard = () => {\n    if (\n      typeof window === \"undefined\" ||\n      !navigator.clipboard.writeText ||\n      !code\n    ) {\n      return;\n    }\n\n    navigator.clipboard.writeText(code).then(() => {\n      setIsCopied(true);\n      onCopy?.();\n\n      setTimeout(() => setIsCopied(false), timeout);\n    }, onError);\n  };\n\n  if (asChild) {\n    return cloneElement(children as ReactElement, {\n      // @ts-expect-error - we know this is a button\n      onClick: copyToClipboard,\n    });\n  }\n\n  const Icon = isCopied ? CheckIcon : CopyIcon;\n\n  return (\n    <Button\n      className={cn(\"shrink-0\", className)}\n      onClick={copyToClipboard}\n      size=\"icon\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children ?? <Icon className=\"text-muted-foreground\" size={14} />}\n    </Button>\n  );\n};\n\ntype CodeBlockFallbackProps = HTMLAttributes<HTMLDivElement>;\n\nconst CodeBlockFallback = ({ children, ...props }: CodeBlockFallbackProps) => (\n  <div {...props}>\n    <pre className=\"w-full\">\n      <code>\n        {children\n          ?.toString()\n          .split(\"\\n\")\n          .map((line, i) => (\n            <span className=\"line\" key={i}>\n              {line}\n            </span>\n          ))}\n      </code>\n    </pre>\n  </div>\n);\n\nexport type CodeBlockBodyProps = Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> & {\n  children: (item: CodeBlockData) => ReactNode;\n};\n\nexport const CodeBlockBody = ({ children, ...props }: CodeBlockBodyProps) => {\n  const { data } = useContext(CodeBlockContext);\n\n  return <div {...props}>{data.map(children)}</div>;\n};\n\nexport type CodeBlockItemProps = HTMLAttributes<HTMLDivElement> & {\n  value: string;\n  lineNumbers?: boolean;\n};\n\nexport const CodeBlockItem = ({\n  children,\n  lineNumbers = true,\n  className,\n  value,\n  ...props\n}: CodeBlockItemProps) => {\n  const { value: activeValue } = useContext(CodeBlockContext);\n\n  if (value !== activeValue) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\n        codeBlockClassName,\n        lineHighlightClassNames,\n        lineDiffClassNames,\n        lineFocusedClassNames,\n        wordHighlightClassNames,\n        darkModeClassNames,\n        lineNumbers && lineNumberClassNames,\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport type CodeBlockContentProps = HTMLAttributes<HTMLDivElement> & {\n  themes?: CodeOptionsMultipleThemes[\"themes\"];\n  language?: BundledLanguage;\n  syntaxHighlighting?: boolean;\n  children: string;\n};\n\nexport const CodeBlockContent = ({\n  children,\n  themes,\n  language,\n  syntaxHighlighting = true,\n  ...props\n}: CodeBlockContentProps) => {\n  const [html, setHtml] = useState<string | null>(null);\n\n  useEffect(() => {\n    if (!syntaxHighlighting) {\n      return;\n    }\n\n    highlight(children as string, language, themes)\n      .then(setHtml)\n      // biome-ignore lint/suspicious/noConsole: \"it's fine\"\n      .catch(console.error);\n  }, [children, themes, syntaxHighlighting, language]);\n\n  if (!(syntaxHighlighting && html)) {\n    return <CodeBlockFallback>{children}</CodeBlockFallback>;\n  }\n\n  return (\n    <div\n      // biome-ignore lint/security/noDangerouslySetInnerHtml: \"Kinda how Shiki works\"\n      dangerouslySetInnerHTML={{ __html: html }}\n      {...props}\n    />\n  );\n};\n","target":"components/kibo-ui/code-block/index.tsx"},{"type":"registry:ui","path":"server.tsx","content":"import {\n  transformerNotationDiff,\n  transformerNotationErrorLevel,\n  transformerNotationFocus,\n  transformerNotationHighlight,\n  transformerNotationWordHighlight,\n} from \"@shikijs/transformers\";\nimport type { HTMLAttributes } from \"react\";\nimport {\n  type BundledLanguage,\n  type CodeOptionsMultipleThemes,\n  codeToHtml,\n} from \"shiki\";\n\nexport type CodeBlockContentProps = HTMLAttributes<HTMLDivElement> & {\n  themes?: CodeOptionsMultipleThemes[\"themes\"];\n  language?: BundledLanguage;\n  children: string;\n  syntaxHighlighting?: boolean;\n};\n\nexport const CodeBlockContent = async ({\n  children,\n  themes,\n  language,\n  syntaxHighlighting = true,\n  ...props\n}: CodeBlockContentProps) => {\n  const html = syntaxHighlighting\n    ? await codeToHtml(children as string, {\n        lang: language ?? \"typescript\",\n        themes: themes ?? {\n          light: \"vitesse-light\",\n          dark: \"vitesse-dark\",\n        },\n        transformers: [\n          transformerNotationDiff({\n            matchAlgorithm: \"v3\",\n          }),\n          transformerNotationHighlight({\n            matchAlgorithm: \"v3\",\n          }),\n          transformerNotationWordHighlight({\n            matchAlgorithm: \"v3\",\n          }),\n          transformerNotationFocus({\n            matchAlgorithm: \"v3\",\n          }),\n          transformerNotationErrorLevel({\n            matchAlgorithm: \"v3\",\n          }),\n        ],\n      })\n    : children;\n\n  return (\n    <div\n      // biome-ignore lint/security/noDangerouslySetInnerHtml: \"Kinda how Shiki works\"\n      dangerouslySetInnerHTML={{ __html: html }}\n      {...props}\n    />\n  );\n};\n","target":"components/kibo-ui/code-block/server.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"color-picker","type":"registry:ui","title":"color-picker","description":"Allows users to select a color. Modeled after the color picker in Figma.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["color","lucide-react","radix-ui"],"devDependencies":["@types/color"],"registryDependencies":["button","input","select"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport Color from \"color\";\nimport { PipetteIcon } from \"lucide-react\";\nimport { Slider } from \"radix-ui\";\nimport {\n  type ComponentProps,\n  createContext,\n  type HTMLAttributes,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { cn } from \"@/lib/utils\";\n\ntype ColorPickerContextValue = {\n  hue: number;\n  saturation: number;\n  lightness: number;\n  alpha: number;\n  mode: string;\n  setHue: (hue: number) => void;\n  setSaturation: (saturation: number) => void;\n  setLightness: (lightness: number) => void;\n  setAlpha: (alpha: number) => void;\n  setMode: (mode: string) => void;\n};\n\nconst ColorPickerContext = createContext<ColorPickerContextValue | undefined>(\n  undefined\n);\n\nexport const useColorPicker = () => {\n  const context = useContext(ColorPickerContext);\n\n  if (!context) {\n    throw new Error(\"useColorPicker must be used within a ColorPickerProvider\");\n  }\n\n  return context;\n};\n\nexport type ColorPickerProps = HTMLAttributes<HTMLDivElement> & {\n  value?: Parameters<typeof Color>[0];\n  defaultValue?: Parameters<typeof Color>[0];\n  onChange?: (value: Parameters<typeof Color.rgb>[0]) => void;\n};\n\nexport const ColorPicker = ({\n  value,\n  defaultValue = \"#000000\",\n  onChange,\n  className,\n  ...props\n}: ColorPickerProps) => {\n  const selectedColor = Color(value);\n  const defaultColor = Color(defaultValue);\n\n  const [hue, setHue] = useState(\n    selectedColor.hue() || defaultColor.hue() || 0\n  );\n  const [saturation, setSaturation] = useState(\n    selectedColor.saturationl() || defaultColor.saturationl() || 100\n  );\n  const [lightness, setLightness] = useState(\n    selectedColor.lightness() || defaultColor.lightness() || 50\n  );\n  const [alpha, setAlpha] = useState(\n    selectedColor.alpha() * 100 || defaultColor.alpha() * 100\n  );\n  const [mode, setMode] = useState(\"hex\");\n\n  // Update color when controlled value changes\n  useEffect(() => {\n    if (value) {\n      const color = Color.rgb(value).rgb().object();\n\n      setHue(color.r);\n      setSaturation(color.g);\n      setLightness(color.b);\n      setAlpha(color.a);\n    }\n  }, [value]);\n\n  // Notify parent of changes\n  useEffect(() => {\n    if (onChange) {\n      const color = Color.hsl(hue, saturation, lightness).alpha(alpha / 100);\n      const rgba = color.rgb().array();\n\n      onChange([rgba[0], rgba[1], rgba[2], alpha / 100]);\n    }\n  }, [hue, saturation, lightness, alpha, onChange]);\n\n  return (\n    <ColorPickerContext.Provider\n      value={{\n        hue,\n        saturation,\n        lightness,\n        alpha,\n        mode,\n        setHue,\n        setSaturation,\n        setLightness,\n        setAlpha,\n        setMode,\n      }}\n    >\n      <div\n        className={cn(\"flex size-full flex-col gap-4\", className)}\n        {...props}\n      />\n    </ColorPickerContext.Provider>\n  );\n};\n\nexport type ColorPickerSelectionProps = HTMLAttributes<HTMLDivElement>;\n\nexport const ColorPickerSelection = memo(\n  ({ className, ...props }: ColorPickerSelectionProps) => {\n    const containerRef = useRef<HTMLDivElement>(null);\n    const [isDragging, setIsDragging] = useState(false);\n    const [positionX, setPositionX] = useState(0);\n    const [positionY, setPositionY] = useState(0);\n    const { hue, setSaturation, setLightness } = useColorPicker();\n\n    const backgroundGradient = useMemo(() => {\n      return `linear-gradient(0deg, rgba(0,0,0,1), rgba(0,0,0,0)),\n            linear-gradient(90deg, rgba(255,255,255,1), rgba(255,255,255,0)),\n            hsl(${hue}, 100%, 50%)`;\n    }, [hue]);\n\n    const handlePointerMove = useCallback(\n      (event: PointerEvent) => {\n        if (!(isDragging && containerRef.current)) {\n          return;\n        }\n        const rect = containerRef.current.getBoundingClientRect();\n        const x = Math.max(\n          0,\n          Math.min(1, (event.clientX - rect.left) / rect.width)\n        );\n        const y = Math.max(\n          0,\n          Math.min(1, (event.clientY - rect.top) / rect.height)\n        );\n        setPositionX(x);\n        setPositionY(y);\n        setSaturation(x * 100);\n        const topLightness = x < 0.01 ? 100 : 50 + 50 * (1 - x);\n        const lightness = topLightness * (1 - y);\n\n        setLightness(lightness);\n      },\n      [isDragging, setSaturation, setLightness]\n    );\n\n    useEffect(() => {\n      const handlePointerUp = () => setIsDragging(false);\n\n      if (isDragging) {\n        window.addEventListener(\"pointermove\", handlePointerMove);\n        window.addEventListener(\"pointerup\", handlePointerUp);\n      }\n\n      return () => {\n        window.removeEventListener(\"pointermove\", handlePointerMove);\n        window.removeEventListener(\"pointerup\", handlePointerUp);\n      };\n    }, [isDragging, handlePointerMove]);\n\n    return (\n      <div\n        className={cn(\"relative size-full cursor-crosshair rounded\", className)}\n        onPointerDown={(e) => {\n          e.preventDefault();\n          setIsDragging(true);\n          handlePointerMove(e.nativeEvent);\n        }}\n        ref={containerRef}\n        style={{\n          background: backgroundGradient,\n        }}\n        {...props}\n      >\n        <div\n          className=\"-translate-x-1/2 -translate-y-1/2 pointer-events-none absolute h-4 w-4 rounded-full border-2 border-white\"\n          style={{\n            left: `${positionX * 100}%`,\n            top: `${positionY * 100}%`,\n            boxShadow: \"0 0 0 1px rgba(0,0,0,0.5)\",\n          }}\n        />\n      </div>\n    );\n  }\n);\n\nColorPickerSelection.displayName = \"ColorPickerSelection\";\n\nexport type ColorPickerHueProps = ComponentProps<typeof Slider.Root>;\n\nexport const ColorPickerHue = ({\n  className,\n  ...props\n}: ColorPickerHueProps) => {\n  const { hue, setHue } = useColorPicker();\n\n  return (\n    <Slider.Root\n      className={cn(\"relative flex h-4 w-full touch-none\", className)}\n      max={360}\n      onValueChange={([hue]) => setHue(hue)}\n      step={1}\n      value={[hue]}\n      {...props}\n    >\n      <Slider.Track className=\"relative my-0.5 h-3 w-full grow rounded-full bg-[linear-gradient(90deg,#FF0000,#FFFF00,#00FF00,#00FFFF,#0000FF,#FF00FF,#FF0000)]\">\n        <Slider.Range className=\"absolute h-full\" />\n      </Slider.Track>\n      <Slider.Thumb className=\"block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\" />\n    </Slider.Root>\n  );\n};\n\nexport type ColorPickerAlphaProps = ComponentProps<typeof Slider.Root>;\n\nexport const ColorPickerAlpha = ({\n  className,\n  ...props\n}: ColorPickerAlphaProps) => {\n  const { alpha, setAlpha } = useColorPicker();\n\n  return (\n    <Slider.Root\n      className={cn(\"relative flex h-4 w-full touch-none\", className)}\n      max={100}\n      onValueChange={([alpha]) => setAlpha(alpha)}\n      step={1}\n      value={[alpha]}\n      {...props}\n    >\n      <Slider.Track className=\"relative my-0.5 h-3 w-full grow rounded-full bg-[url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==')] bg-center bg-repeat-x dark:bg-[url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAALklEQVR4nGP8+vWrCAMewM3N/QafPBM+SWLAqAGDwQBGQgoIpZOB98KoAVQwAADxzQcSVIRCfQAAAABJRU5ErkJggg==')]\">\n        <div className=\"absolute inset-0 rounded-full bg-gradient-to-r from-transparent to-black/50 dark:to-white/50\" />\n        <Slider.Range className=\"absolute h-full rounded-full bg-transparent\" />\n      </Slider.Track>\n      <Slider.Thumb className=\"block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\" />\n    </Slider.Root>\n  );\n};\n\nexport type ColorPickerEyeDropperProps = ComponentProps<typeof Button>;\n\nexport const ColorPickerEyeDropper = ({\n  className,\n  ...props\n}: ColorPickerEyeDropperProps) => {\n  const { setHue, setSaturation, setLightness, setAlpha } = useColorPicker();\n\n  const handleEyeDropper = async () => {\n    try {\n      // @ts-expect-error - EyeDropper API is experimental\n      const eyeDropper = new EyeDropper();\n      const result = await eyeDropper.open();\n      const color = Color(result.sRGBHex);\n      const [h, s, l] = color.hsl().array();\n\n      setHue(h);\n      setSaturation(s);\n      setLightness(l);\n      setAlpha(100);\n    } catch (error) {\n      console.error(\"EyeDropper failed:\", error);\n    }\n  };\n\n  return (\n    <Button\n      className={cn(\"shrink-0 text-muted-foreground\", className)}\n      onClick={handleEyeDropper}\n      size=\"icon\"\n      type=\"button\"\n      variant=\"outline\"\n      {...props}\n    >\n      <PipetteIcon size={16} />\n    </Button>\n  );\n};\n\nexport type ColorPickerOutputProps = ComponentProps<typeof SelectTrigger>;\n\nconst formats = [\"hex\", \"rgb\", \"css\", \"hsl\"];\n\nexport const ColorPickerOutput = ({\n  className,\n  ...props\n}: ColorPickerOutputProps) => {\n  const { mode, setMode } = useColorPicker();\n\n  return (\n    <Select onValueChange={setMode} value={mode}>\n      <SelectTrigger className=\"h-8 w-20 shrink-0 text-xs\" {...props}>\n        <SelectValue placeholder=\"Mode\" />\n      </SelectTrigger>\n      <SelectContent>\n        {formats.map((format) => (\n          <SelectItem className=\"text-xs\" key={format} value={format}>\n            {format.toUpperCase()}\n          </SelectItem>\n        ))}\n      </SelectContent>\n    </Select>\n  );\n};\n\ntype PercentageInputProps = ComponentProps<typeof Input>;\n\nconst PercentageInput = ({ className, ...props }: PercentageInputProps) => {\n  return (\n    <div className=\"relative\">\n      <Input\n        readOnly\n        type=\"text\"\n        {...props}\n        className={cn(\n          \"h-8 w-[3.25rem] rounded-l-none bg-secondary px-2 text-xs shadow-none\",\n          className\n        )}\n      />\n      <span className=\"-translate-y-1/2 absolute top-1/2 right-2 text-muted-foreground text-xs\">\n        %\n      </span>\n    </div>\n  );\n};\n\nexport type ColorPickerFormatProps = HTMLAttributes<HTMLDivElement>;\n\nexport const ColorPickerFormat = ({\n  className,\n  ...props\n}: ColorPickerFormatProps) => {\n  const { hue, saturation, lightness, alpha, mode } = useColorPicker();\n  const color = Color.hsl(hue, saturation, lightness, alpha / 100);\n\n  if (mode === \"hex\") {\n    const hex = color.hex();\n\n    return (\n      <div\n        className={cn(\n          \"-space-x-px relative flex w-full items-center rounded-md shadow-sm\",\n          className\n        )}\n        {...props}\n      >\n        <Input\n          className=\"h-8 rounded-r-none bg-secondary px-2 text-xs shadow-none\"\n          readOnly\n          type=\"text\"\n          value={hex}\n        />\n        <PercentageInput value={alpha} />\n      </div>\n    );\n  }\n\n  if (mode === \"rgb\") {\n    const rgb = color\n      .rgb()\n      .array()\n      .map((value) => Math.round(value));\n\n    return (\n      <div\n        className={cn(\n          \"-space-x-px flex items-center rounded-md shadow-sm\",\n          className\n        )}\n        {...props}\n      >\n        {rgb.map((value, index) => (\n          <Input\n            className={cn(\n              \"h-8 rounded-r-none bg-secondary px-2 text-xs shadow-none\",\n              index && \"rounded-l-none\",\n              className\n            )}\n            key={index}\n            readOnly\n            type=\"text\"\n            value={value}\n          />\n        ))}\n        <PercentageInput value={alpha} />\n      </div>\n    );\n  }\n\n  if (mode === \"css\") {\n    const rgb = color\n      .rgb()\n      .array()\n      .map((value) => Math.round(value));\n\n    return (\n      <div className={cn(\"w-full rounded-md shadow-sm\", className)} {...props}>\n        <Input\n          className=\"h-8 w-full bg-secondary px-2 text-xs shadow-none\"\n          readOnly\n          type=\"text\"\n          value={`rgba(${rgb.join(\", \")}, ${alpha}%)`}\n          {...props}\n        />\n      </div>\n    );\n  }\n\n  if (mode === \"hsl\") {\n    const hsl = color\n      .hsl()\n      .array()\n      .map((value) => Math.round(value));\n\n    return (\n      <div\n        className={cn(\n          \"-space-x-px flex items-center rounded-md shadow-sm\",\n          className\n        )}\n        {...props}\n      >\n        {hsl.map((value, index) => (\n          <Input\n            className={cn(\n              \"h-8 rounded-r-none bg-secondary px-2 text-xs shadow-none\",\n              index && \"rounded-l-none\",\n              className\n            )}\n            key={index}\n            readOnly\n            type=\"text\"\n            value={value}\n          />\n        ))}\n        <PercentageInput value={alpha} />\n      </div>\n    );\n  }\n\n  return null;\n};\n","target":"components/kibo-ui/color-picker/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"combobox","type":"registry:ui","title":"combobox","description":"Autocomplete input and command palette with a list of suggestions.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@radix-ui/react-use-controllable-state","lucide-react"],"devDependencies":[],"registryDependencies":["button","command","popover"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport { ChevronsUpDownIcon, PlusIcon } from \"lucide-react\";\nimport {\n  type ComponentProps,\n  createContext,\n  type ReactNode,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n  CommandSeparator,\n} from \"@/components/ui/command\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { cn } from \"@/lib/utils\";\n\ntype ComboboxData = {\n  label: string;\n  value: string;\n};\n\ntype ComboboxContextType = {\n  data: ComboboxData[];\n  type: string;\n  value: string;\n  onValueChange: (value: string) => void;\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  width: number;\n  setWidth: (width: number) => void;\n  inputValue: string;\n  setInputValue: (value: string) => void;\n};\n\nconst ComboboxContext = createContext<ComboboxContextType>({\n  data: [],\n  type: \"item\",\n  value: \"\",\n  onValueChange: () => {},\n  open: false,\n  onOpenChange: () => {},\n  width: 200,\n  setWidth: () => {},\n  inputValue: \"\",\n  setInputValue: () => {},\n});\n\nexport type ComboboxProps = ComponentProps<typeof Popover> & {\n  data: ComboboxData[];\n  type: string;\n  defaultValue?: string;\n  value?: string;\n  onValueChange?: (value: string) => void;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n};\n\nexport const Combobox = ({\n  data,\n  type,\n  defaultValue,\n  value: controlledValue,\n  onValueChange: controlledOnValueChange,\n  defaultOpen = false,\n  open: controlledOpen,\n  onOpenChange: controlledOnOpenChange,\n  ...props\n}: ComboboxProps) => {\n  const [value, onValueChange] = useControllableState({\n    defaultProp: defaultValue ?? \"\",\n    prop: controlledValue,\n    onChange: controlledOnValueChange,\n  });\n  const [open, onOpenChange] = useControllableState({\n    defaultProp: defaultOpen,\n    prop: controlledOpen,\n    onChange: controlledOnOpenChange,\n  });\n  const [width, setWidth] = useState(200);\n  const [inputValue, setInputValue] = useState(\"\");\n\n  return (\n    <ComboboxContext.Provider\n      value={{\n        type,\n        value,\n        onValueChange,\n        open,\n        onOpenChange,\n        data,\n        width,\n        setWidth,\n        inputValue,\n        setInputValue,\n      }}\n    >\n      <Popover {...props} onOpenChange={onOpenChange} open={open} />\n    </ComboboxContext.Provider>\n  );\n};\n\nexport type ComboboxTriggerProps = ComponentProps<typeof Button>;\n\nexport const ComboboxTrigger = ({\n  children,\n  ...props\n}: ComboboxTriggerProps) => {\n  const { value, data, type, setWidth } = useContext(ComboboxContext);\n  const ref = useRef<HTMLButtonElement>(null);\n\n  useEffect(() => {\n    // Create a ResizeObserver to detect width changes\n    const resizeObserver = new ResizeObserver((entries) => {\n      for (const entry of entries) {\n        const newWidth = (entry.target as HTMLElement).offsetWidth;\n        if (newWidth) {\n          setWidth?.(newWidth);\n        }\n      }\n    });\n\n    if (ref.current) {\n      resizeObserver.observe(ref.current);\n    }\n\n    // Clean up the observer when component unmounts\n    return () => {\n      resizeObserver.disconnect();\n    };\n  }, [setWidth]);\n\n  return (\n    <PopoverTrigger asChild>\n      <Button variant=\"outline\" {...props} ref={ref}>\n        {children ?? (\n          <span className=\"flex w-full items-center justify-between gap-2\">\n            {value\n              ? data.find((item) => item.value === value)?.label\n              : `Select ${type}...`}\n            <ChevronsUpDownIcon\n              className=\"shrink-0 text-muted-foreground\"\n              size={16}\n            />\n          </span>\n        )}\n      </Button>\n    </PopoverTrigger>\n  );\n};\n\nexport type ComboboxContentProps = ComponentProps<typeof Command> & {\n  popoverOptions?: ComponentProps<typeof PopoverContent>;\n};\n\nexport const ComboboxContent = ({\n  className,\n  popoverOptions,\n  ...props\n}: ComboboxContentProps) => {\n  const { width } = useContext(ComboboxContext);\n\n  return (\n    <PopoverContent\n      className={cn(\"p-0\", className)}\n      style={{ width }}\n      {...popoverOptions}\n    >\n      <Command {...props} />\n    </PopoverContent>\n  );\n};\n\nexport type ComboboxInputProps = ComponentProps<typeof CommandInput> & {\n  value?: string;\n  defaultValue?: string;\n  onValueChange?: (value: string) => void;\n};\n\nexport const ComboboxInput = ({\n  value: controlledValue,\n  defaultValue,\n  onValueChange: controlledOnValueChange,\n  ...props\n}: ComboboxInputProps) => {\n  const { type, inputValue, setInputValue } = useContext(ComboboxContext);\n\n  const [value, onValueChange] = useControllableState({\n    defaultProp: defaultValue ?? inputValue,\n    prop: controlledValue,\n    onChange: (newValue) => {\n      // Sync with context state\n      setInputValue(newValue);\n      // Call external onChange if provided\n      controlledOnValueChange?.(newValue);\n    },\n  });\n\n  return (\n    <CommandInput\n      onValueChange={onValueChange}\n      placeholder={`Search ${type}...`}\n      value={value}\n      {...props}\n    />\n  );\n};\n\nexport type ComboboxListProps = ComponentProps<typeof CommandList>;\n\nexport const ComboboxList = (props: ComboboxListProps) => (\n  <CommandList {...props} />\n);\n\nexport type ComboboxEmptyProps = ComponentProps<typeof CommandEmpty>;\n\nexport const ComboboxEmpty = ({ children, ...props }: ComboboxEmptyProps) => {\n  const { type } = useContext(ComboboxContext);\n\n  return (\n    <CommandEmpty {...props}>{children ?? `No ${type} found.`}</CommandEmpty>\n  );\n};\n\nexport type ComboboxGroupProps = ComponentProps<typeof CommandGroup>;\n\nexport const ComboboxGroup = (props: ComboboxGroupProps) => (\n  <CommandGroup {...props} />\n);\n\nexport type ComboboxItemProps = ComponentProps<typeof CommandItem>;\n\nexport const ComboboxItem = (props: ComboboxItemProps) => {\n  const { onValueChange, onOpenChange } = useContext(ComboboxContext);\n\n  return (\n    <CommandItem\n      onSelect={(currentValue) => {\n        onValueChange(currentValue);\n        onOpenChange(false);\n      }}\n      {...props}\n    />\n  );\n};\n\nexport type ComboboxSeparatorProps = ComponentProps<typeof CommandSeparator>;\n\nexport const ComboboxSeparator = (props: ComboboxSeparatorProps) => (\n  <CommandSeparator {...props} />\n);\n\nexport type ComboboxCreateNewProps = {\n  onCreateNew: (value: string) => void;\n  children?: (inputValue: string) => ReactNode;\n  className?: string;\n};\n\nexport const ComboboxCreateNew = ({\n  onCreateNew,\n  children,\n  className,\n}: ComboboxCreateNewProps) => {\n  const { inputValue, type, onValueChange, onOpenChange } =\n    useContext(ComboboxContext);\n\n  if (!inputValue.trim()) {\n    return null;\n  }\n\n  const handleCreateNew = () => {\n    onCreateNew(inputValue.trim());\n    onValueChange(inputValue.trim());\n    onOpenChange(false);\n  };\n\n  return (\n    <button\n      className={cn(\n        \"relative flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n        className\n      )}\n      onClick={handleCreateNew}\n      type=\"button\"\n    >\n      {children ? (\n        children(inputValue)\n      ) : (\n        <>\n          <PlusIcon className=\"h-4 w-4 text-muted-foreground\" />\n          <span>{`Create new ${type}: \"${inputValue}\"`}</span>\n        </>\n      )}\n    </button>\n  );\n};\n","target":"components/kibo-ui/combobox/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"comparison","type":"registry:ui","title":"comparison","description":"A slider-based component for comparing two items in an overlay.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["lucide-react","motion"],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { GripVerticalIcon } from \"lucide-react\";\nimport {\n  type MotionValue,\n  motion,\n  useMotionValue,\n  useSpring,\n  useTransform,\n} from \"motion/react\";\nimport {\n  type ComponentProps,\n  createContext,\n  type HTMLAttributes,\n  type MouseEventHandler,\n  type ReactNode,\n  type TouchEventHandler,\n  useContext,\n  useState,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ntype ImageComparisonContextType = {\n  sliderPosition: number;\n  setSliderPosition: (pos: number) => void;\n  motionSliderPosition: MotionValue<number>;\n  mode: \"hover\" | \"drag\";\n};\n\nconst ImageComparisonContext = createContext<\n  ImageComparisonContextType | undefined\n>(undefined);\n\nconst useImageComparisonContext = () => {\n  const context = useContext(ImageComparisonContext);\n\n  if (!context) {\n    throw new Error(\n      \"useImageComparisonContext must be used within a ImageComparison\"\n    );\n  }\n\n  return context;\n};\n\nexport type ComparisonProps = HTMLAttributes<HTMLDivElement> & {\n  mode?: \"hover\" | \"drag\";\n  onDragStart?: () => void;\n  onDragEnd?: () => void;\n};\n\nexport const Comparison = ({\n  className,\n  mode = \"drag\",\n  onDragStart,\n  onDragEnd,\n  ...props\n}: ComparisonProps) => {\n  const [isDragging, setIsDragging] = useState(false);\n  const motionValue = useMotionValue(50);\n  const motionSliderPosition = useSpring(motionValue, {\n    bounce: 0,\n    duration: 0,\n  });\n  const [sliderPosition, setSliderPosition] = useState(50);\n\n  const handleDrag = (domRect: DOMRect, clientX: number) => {\n    if (!isDragging && mode === \"drag\") {\n      return;\n    }\n\n    const x = clientX - domRect.left;\n    const percentage = Math.min(Math.max((x / domRect.width) * 100, 0), 100);\n    motionValue.set(percentage);\n    setSliderPosition(percentage);\n  };\n\n  const handleMouseDrag: MouseEventHandler<HTMLDivElement> = (event) => {\n    if (!event) {\n      return;\n    }\n\n    const containerRect = event.currentTarget.getBoundingClientRect();\n\n    handleDrag(containerRect, event.clientX);\n  };\n\n  const handleTouchDrag: TouchEventHandler<HTMLDivElement> = (event) => {\n    if (!event) {\n      return;\n    }\n\n    const containerRect = event.currentTarget.getBoundingClientRect();\n    const touches = Array.from(event.touches);\n\n    handleDrag(containerRect, touches.at(0)?.clientX ?? 0);\n  };\n\n  const handleDragStart = () => {\n    if (mode === \"drag\") {\n      setIsDragging(true);\n      onDragStart?.();\n    }\n  };\n\n  const handleDragEnd = () => {\n    if (mode === \"drag\") {\n      setIsDragging(false);\n      onDragEnd?.();\n    }\n  };\n\n  return (\n    <ImageComparisonContext.Provider\n      value={{ sliderPosition, setSliderPosition, motionSliderPosition, mode }}\n    >\n      <div\n        aria-label=\"Comparison slider\"\n        aria-valuemax={100}\n        aria-valuemin={0}\n        aria-valuenow={sliderPosition}\n        className={cn(\n          \"relative isolate w-full select-none overflow-hidden\",\n          className\n        )}\n        onMouseDown={handleDragStart}\n        onMouseLeave={handleDragEnd}\n        onMouseMove={handleMouseDrag}\n        onMouseUp={handleDragEnd}\n        onTouchEnd={handleDragEnd}\n        onTouchMove={handleTouchDrag}\n        onTouchStart={handleDragStart}\n        role=\"slider\"\n        tabIndex={0}\n        {...props}\n      />\n    </ImageComparisonContext.Provider>\n  );\n};\n\nexport type ComparisonItemProps = ComponentProps<typeof motion.div> & {\n  position: \"left\" | \"right\";\n};\n\nexport const ComparisonItem = ({\n  className,\n  position,\n  ...props\n}: ComparisonItemProps) => {\n  const { motionSliderPosition } = useImageComparisonContext();\n  const leftClipPath = useTransform(\n    motionSliderPosition,\n    (value) => `inset(0 0 0 ${value}%)`\n  );\n  const rightClipPath = useTransform(\n    motionSliderPosition,\n    (value) => `inset(0 ${100 - value}% 0 0)`\n  );\n\n  return (\n    <motion.div\n      aria-hidden=\"true\"\n      className={cn(\"absolute inset-0 h-full w-full object-cover\", className)}\n      role=\"img\"\n      style={{\n        clipPath: position === \"left\" ? leftClipPath : rightClipPath,\n      }}\n      {...props}\n    />\n  );\n};\n\nexport type ComparisonHandleProps = ComponentProps<typeof motion.div> & {\n  children?: ReactNode;\n};\n\nexport const ComparisonHandle = ({\n  className,\n  children,\n  ...props\n}: ComparisonHandleProps) => {\n  const { motionSliderPosition, mode } = useImageComparisonContext();\n  const left = useTransform(motionSliderPosition, (value) => `${value}%`);\n\n  return (\n    <motion.div\n      aria-hidden=\"true\"\n      className={cn(\n        \"-translate-x-1/2 absolute top-0 z-50 flex h-full w-10 items-center justify-center\",\n        mode === \"drag\" && \"cursor-grab active:cursor-grabbing\",\n        className\n      )}\n      role=\"presentation\"\n      style={{ left }}\n      {...props}\n    >\n      {children ?? (\n        <>\n          <div className=\"-translate-x-1/2 absolute left-1/2 h-full w-1 bg-background\" />\n          {mode === \"drag\" && (\n            <div className=\"z-50 flex items-center justify-center rounded-sm bg-background px-0.5 py-1\">\n              <GripVerticalIcon className=\"h-4 w-4 select-none text-muted-foreground\" />\n            </div>\n          )}\n        </>\n      )}\n    </motion.div>\n  );\n};\n","target":"components/kibo-ui/comparison/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"contribution-graph","type":"registry:ui","title":"contribution-graph","description":"A GitHub-style contribution graph component that displays activity levels over time.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["date-fns"],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport type { Day as WeekDay } from \"date-fns\";\nimport {\n  differenceInCalendarDays,\n  eachDayOfInterval,\n  formatISO,\n  getDay,\n  getMonth,\n  getYear,\n  nextDay,\n  parseISO,\n  subWeeks,\n} from \"date-fns\";\nimport {\n  type CSSProperties,\n  createContext,\n  Fragment,\n  type HTMLAttributes,\n  type ReactNode,\n  useContext,\n  useMemo,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type Activity = {\n  date: string;\n  count: number;\n  level: number;\n};\n\ntype Week = Array<Activity | undefined>;\n\nexport type Labels = {\n  months?: string[];\n  weekdays?: string[];\n  totalCount?: string;\n  legend?: {\n    less?: string;\n    more?: string;\n  };\n};\n\ntype MonthLabel = {\n  weekIndex: number;\n  label: string;\n};\n\nconst DEFAULT_MONTH_LABELS = [\n  \"Jan\",\n  \"Feb\",\n  \"Mar\",\n  \"Apr\",\n  \"May\",\n  \"Jun\",\n  \"Jul\",\n  \"Aug\",\n  \"Sep\",\n  \"Oct\",\n  \"Nov\",\n  \"Dec\",\n];\n\nconst DEFAULT_LABELS: Labels = {\n  months: DEFAULT_MONTH_LABELS,\n  weekdays: [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"],\n  totalCount: \"{{count}} activities in {{year}}\",\n  legend: {\n    less: \"Less\",\n    more: \"More\",\n  },\n};\n\ntype ContributionGraphContextType = {\n  data: Activity[];\n  weeks: Week[];\n  blockMargin: number;\n  blockRadius: number;\n  blockSize: number;\n  fontSize: number;\n  labels: Labels;\n  labelHeight: number;\n  maxLevel: number;\n  totalCount: number;\n  weekStart: WeekDay;\n  year: number;\n  width: number;\n  height: number;\n};\n\nconst ContributionGraphContext =\n  createContext<ContributionGraphContextType | null>(null);\n\nconst useContributionGraph = () => {\n  const context = useContext(ContributionGraphContext);\n\n  if (!context) {\n    throw new Error(\n      \"ContributionGraph components must be used within a ContributionGraph\"\n    );\n  }\n\n  return context;\n};\n\nconst fillHoles = (activities: Activity[]): Activity[] => {\n  if (activities.length === 0) {\n    return [];\n  }\n\n  // Sort activities by date to ensure correct date range\n  const sortedActivities = [...activities].sort((a, b) =>\n    a.date.localeCompare(b.date)\n  );\n\n  const calendar = new Map<string, Activity>(\n    activities.map((a) => [a.date, a])\n  );\n\n  const firstActivity = sortedActivities[0] as Activity;\n  const lastActivity = sortedActivities.at(-1);\n\n  if (!lastActivity) {\n    return [];\n  }\n\n  return eachDayOfInterval({\n    start: parseISO(firstActivity.date),\n    end: parseISO(lastActivity.date),\n  }).map((day) => {\n    const date = formatISO(day, { representation: \"date\" });\n\n    if (calendar.has(date)) {\n      return calendar.get(date) as Activity;\n    }\n\n    return {\n      date,\n      count: 0,\n      level: 0,\n    };\n  });\n};\n\nconst groupByWeeks = (\n  activities: Activity[],\n  weekStart: WeekDay = 0\n): Week[] => {\n  if (activities.length === 0) {\n    return [];\n  }\n\n  const normalizedActivities = fillHoles(activities);\n  const firstActivity = normalizedActivities[0] as Activity;\n  const firstDate = parseISO(firstActivity.date);\n  const firstCalendarDate =\n    getDay(firstDate) === weekStart\n      ? firstDate\n      : subWeeks(nextDay(firstDate, weekStart), 1);\n\n  const paddedActivities = [\n    ...(new Array(differenceInCalendarDays(firstDate, firstCalendarDate)).fill(\n      undefined\n    ) as Activity[]),\n    ...normalizedActivities,\n  ];\n\n  const numberOfWeeks = Math.ceil(paddedActivities.length / 7);\n\n  return new Array(numberOfWeeks)\n    .fill(undefined)\n    .map((_, weekIndex) =>\n      paddedActivities.slice(weekIndex * 7, weekIndex * 7 + 7)\n    );\n};\n\nconst getMonthLabels = (\n  weeks: Week[],\n  monthNames: string[] = DEFAULT_MONTH_LABELS\n): MonthLabel[] => {\n  return weeks\n    .reduce<MonthLabel[]>((labels, week, weekIndex) => {\n      const firstActivity = week.find((activity) => activity !== undefined);\n\n      if (!firstActivity) {\n        throw new Error(\n          `Unexpected error: Week ${weekIndex + 1} is empty: [${week}].`\n        );\n      }\n\n      const month = monthNames[getMonth(parseISO(firstActivity.date))];\n\n      if (!month) {\n        const monthName = new Date(firstActivity.date).toLocaleString(\"en-US\", {\n          month: \"short\",\n        });\n        throw new Error(\n          `Unexpected error: undefined month label for ${monthName}.`\n        );\n      }\n\n      const prevLabel = labels.at(-1);\n\n      if (weekIndex === 0 || !prevLabel || prevLabel.label !== month) {\n        return labels.concat({ weekIndex, label: month });\n      }\n\n      return labels;\n    }, [])\n    .filter(({ weekIndex }, index, labels) => {\n      const minWeeks = 3;\n\n      if (index === 0) {\n        return labels[1] && labels[1].weekIndex - weekIndex >= minWeeks;\n      }\n\n      if (index === labels.length - 1) {\n        return weeks.slice(weekIndex).length >= minWeeks;\n      }\n\n      return true;\n    });\n};\n\nexport type ContributionGraphProps = HTMLAttributes<HTMLDivElement> & {\n  data: Activity[];\n  blockMargin?: number;\n  blockRadius?: number;\n  blockSize?: number;\n  fontSize?: number;\n  labels?: Labels;\n  maxLevel?: number;\n  style?: CSSProperties;\n  totalCount?: number;\n  weekStart?: WeekDay;\n  children: ReactNode;\n  className?: string;\n};\n\nexport const ContributionGraph = ({\n  data,\n  blockMargin = 4,\n  blockRadius = 2,\n  blockSize = 12,\n  fontSize = 14,\n  labels: labelsProp = undefined,\n  maxLevel: maxLevelProp = 4,\n  style = {},\n  totalCount: totalCountProp = undefined,\n  weekStart = 0,\n  className,\n  ...props\n}: ContributionGraphProps) => {\n  const maxLevel = Math.max(1, maxLevelProp);\n  const weeks = useMemo(() => groupByWeeks(data, weekStart), [data, weekStart]);\n  const LABEL_MARGIN = 8;\n\n  const labels = { ...DEFAULT_LABELS, ...labelsProp };\n  const labelHeight = fontSize + LABEL_MARGIN;\n\n  const year =\n    data.length > 0\n      ? getYear(parseISO(data[0].date))\n      : new Date().getFullYear();\n\n  const totalCount =\n    typeof totalCountProp === \"number\"\n      ? totalCountProp\n      : data.reduce((sum, activity) => sum + activity.count, 0);\n\n  const width = weeks.length * (blockSize + blockMargin) - blockMargin;\n  const height = labelHeight + (blockSize + blockMargin) * 7 - blockMargin;\n\n  if (data.length === 0) {\n    return null;\n  }\n\n  return (\n    <ContributionGraphContext.Provider\n      value={{\n        data,\n        weeks,\n        blockMargin,\n        blockRadius,\n        blockSize,\n        fontSize,\n        labels,\n        labelHeight,\n        maxLevel,\n        totalCount,\n        weekStart,\n        year,\n        width,\n        height,\n      }}\n    >\n      <div\n        className={cn(\"flex w-max max-w-full flex-col gap-2\", className)}\n        style={{ fontSize, ...style }}\n        {...props}\n      />\n    </ContributionGraphContext.Provider>\n  );\n};\n\nexport type ContributionGraphBlockProps = HTMLAttributes<SVGRectElement> & {\n  activity: Activity;\n  dayIndex: number;\n  weekIndex: number;\n};\n\nexport const ContributionGraphBlock = ({\n  activity,\n  dayIndex,\n  weekIndex,\n  className,\n  ...props\n}: ContributionGraphBlockProps) => {\n  const { blockSize, blockMargin, blockRadius, labelHeight, maxLevel } =\n    useContributionGraph();\n\n  if (activity.level < 0 || activity.level > maxLevel) {\n    throw new RangeError(\n      `Provided activity level ${activity.level} for ${activity.date} is out of range. It must be between 0 and ${maxLevel}.`\n    );\n  }\n\n  return (\n    <rect\n      className={cn(\n        'data-[level=\"0\"]:fill-muted',\n        'data-[level=\"1\"]:fill-muted-foreground/20',\n        'data-[level=\"2\"]:fill-muted-foreground/40',\n        'data-[level=\"3\"]:fill-muted-foreground/60',\n        'data-[level=\"4\"]:fill-muted-foreground/80',\n        className\n      )}\n      data-count={activity.count}\n      data-date={activity.date}\n      data-level={activity.level}\n      height={blockSize}\n      rx={blockRadius}\n      ry={blockRadius}\n      width={blockSize}\n      x={(blockSize + blockMargin) * weekIndex}\n      y={labelHeight + (blockSize + blockMargin) * dayIndex}\n      {...props}\n    />\n  );\n};\n\nexport type ContributionGraphCalendarProps = Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> & {\n  hideMonthLabels?: boolean;\n  className?: string;\n  children: (props: {\n    activity: Activity;\n    dayIndex: number;\n    weekIndex: number;\n  }) => ReactNode;\n};\n\nexport const ContributionGraphCalendar = ({\n  hideMonthLabels = false,\n  className,\n  children,\n  ...props\n}: ContributionGraphCalendarProps) => {\n  const { weeks, width, height, blockSize, blockMargin, labels } =\n    useContributionGraph();\n\n  const monthLabels = useMemo(\n    () => getMonthLabels(weeks, labels.months),\n    [weeks, labels.months]\n  );\n\n  return (\n    <div\n      className={cn(\"max-w-full overflow-x-auto overflow-y-hidden\", className)}\n      {...props}\n    >\n      <svg\n        className=\"block overflow-visible\"\n        height={height}\n        viewBox={`0 0 ${width} ${height}`}\n        width={width}\n      >\n        <title>Contribution Graph</title>\n        {!hideMonthLabels && (\n          <g className=\"fill-current\">\n            {monthLabels.map(({ label, weekIndex }) => (\n              <text\n                dominantBaseline=\"hanging\"\n                key={weekIndex}\n                x={(blockSize + blockMargin) * weekIndex}\n              >\n                {label}\n              </text>\n            ))}\n          </g>\n        )}\n        {weeks.map((week, weekIndex) =>\n          week.map((activity, dayIndex) => {\n            if (!activity) {\n              return null;\n            }\n\n            return (\n              <Fragment key={`${weekIndex}-${dayIndex}`}>\n                {children({ activity, dayIndex, weekIndex })}\n              </Fragment>\n            );\n          })\n        )}\n      </svg>\n    </div>\n  );\n};\n\nexport type ContributionGraphFooterProps = HTMLAttributes<HTMLDivElement>;\n\nexport const ContributionGraphFooter = ({\n  className,\n  ...props\n}: ContributionGraphFooterProps) => (\n  <div\n    className={cn(\n      \"flex flex-wrap gap-1 whitespace-nowrap sm:gap-x-4\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type ContributionGraphTotalCountProps = Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> & {\n  children?: (props: { totalCount: number; year: number }) => ReactNode;\n};\n\nexport const ContributionGraphTotalCount = ({\n  className,\n  children,\n  ...props\n}: ContributionGraphTotalCountProps) => {\n  const { totalCount, year, labels } = useContributionGraph();\n\n  if (children) {\n    return <>{children({ totalCount, year })}</>;\n  }\n\n  return (\n    <div className={cn(\"text-muted-foreground\", className)} {...props}>\n      {labels.totalCount\n        ? labels.totalCount\n            .replace(\"{{count}}\", String(totalCount))\n            .replace(\"{{year}}\", String(year))\n        : `${totalCount} activities in ${year}`}\n    </div>\n  );\n};\n\nexport type ContributionGraphLegendProps = Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> & {\n  children?: (props: { level: number }) => ReactNode;\n};\n\nexport const ContributionGraphLegend = ({\n  className,\n  children,\n  ...props\n}: ContributionGraphLegendProps) => {\n  const { labels, maxLevel, blockSize, blockRadius } = useContributionGraph();\n\n  return (\n    <div\n      className={cn(\"ml-auto flex items-center gap-[3px]\", className)}\n      {...props}\n    >\n      <span className=\"mr-1 text-muted-foreground\">\n        {labels.legend?.less || \"Less\"}\n      </span>\n      {new Array(maxLevel + 1).fill(undefined).map((_, level) =>\n        children ? (\n          <Fragment key={level}>{children({ level })}</Fragment>\n        ) : (\n          <svg height={blockSize} key={level} width={blockSize}>\n            <title>{`${level} contributions`}</title>\n            <rect\n              className={cn(\n                \"stroke-[1px] stroke-border\",\n                'data-[level=\"0\"]:fill-muted',\n                'data-[level=\"1\"]:fill-muted-foreground/20',\n                'data-[level=\"2\"]:fill-muted-foreground/40',\n                'data-[level=\"3\"]:fill-muted-foreground/60',\n                'data-[level=\"4\"]:fill-muted-foreground/80'\n              )}\n              data-level={level}\n              height={blockSize}\n              rx={blockRadius}\n              ry={blockRadius}\n              width={blockSize}\n            />\n          </svg>\n        )\n      )}\n      <span className=\"ml-1 text-muted-foreground\">\n        {labels.legend?.more || \"More\"}\n      </span>\n    </div>\n  );\n};\n","target":"components/kibo-ui/contribution-graph/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"credit-card","type":"registry:ui","title":"credit-card","description":"Credit card components for displaying and validating credit card information.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["react-svg-credit-card-payment-icons"],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport {\n  type ComponentProps,\n  createContext,\n  type HTMLAttributes,\n  type ReactNode,\n  useContext,\n  useEffect,\n  useState,\n} from \"react\";\nimport { PaymentIcon } from \"react-svg-credit-card-payment-icons\";\nimport { cn } from \"@/lib/utils\";\n\nconst useSupportsHover = () => {\n  const [supportsHover, setSupportsHover] = useState(false);\n\n  useEffect(() => {\n    const mql = window.matchMedia(\"(hover: hover)\");\n    const handler = (e: MediaQueryListEvent) => setSupportsHover(e.matches);\n\n    setSupportsHover(mql.matches);\n    mql.addEventListener(\"change\", handler);\n\n    return () => mql.removeEventListener(\"change\", handler);\n  }, []);\n\n  return supportsHover;\n};\n\nexport type CreditCardProps = HTMLAttributes<HTMLDivElement>;\n\nexport const CreditCard = ({ className, ...props }: CreditCardProps) => (\n  <div\n    className={cn(\n      \"group/kibo-credit-card perspective-distant aspect-[8560/5398] w-full max-w-96 text-white\",\n      \"@container\",\n      className\n    )}\n    {...props}\n  />\n);\n\nconst CreditCardFlipContext = createContext(false);\n\nexport type CreditCardFlipperProps = HTMLAttributes<HTMLDivElement>;\n\nexport const CreditCardFlipper = ({\n  className,\n  children,\n  ...props\n}: CreditCardFlipperProps & { children?: ReactNode }) => {\n  const supportsHover = useSupportsHover();\n  const [isFlipped, setIsFlipped] = useState(false);\n\n  const handleClick = () => {\n    if (!supportsHover) {\n      setIsFlipped((prev) => !prev);\n    }\n  };\n\n  return (\n    <CreditCardFlipContext.Provider value={true}>\n      {/* biome-ignore lint/nursery/noStaticElementInteractions: tap to flip for touch devices */}\n      <div\n        aria-label=\"Flip credit card\"\n        className={cn(\n          \"h-full w-full\",\n          \"@xs:rounded-2xl rounded-lg\",\n          \"transform-3d transition duration-700 ease-in-out\",\n          supportsHover &&\n            \"group-hover/kibo-credit-card:-rotate-y-180 group-hover/kibo-credit-card:shadow-lg\",\n          !supportsHover && isFlipped && \"-rotate-y-180 shadow-lg\",\n          className\n        )}\n        onClick={handleClick}\n        {...props}\n      >\n        {children}\n      </div>\n    </CreditCardFlipContext.Provider>\n  );\n};\n\nexport type CreditCardNameProps = HTMLAttributes<HTMLParagraphElement>;\n\nexport const CreditCardName = ({\n  className,\n  style,\n  ...props\n}: CreditCardNameProps) => (\n  <p\n    className={cn(\"font-semibold uppercase\", className)}\n    style={{\n      lineHeight: \"100%\",\n      ...style,\n    }}\n    {...props}\n  />\n);\n\nexport type CreditCardChipProps = HTMLAttributes<SVGSVGElement>;\n\nexport const CreditCardChip = ({\n  className,\n  children,\n  ...props\n}: CreditCardChipProps) =>\n  children ? (\n    <div\n      className={cn(\n        \"-translate-y-1/2 absolute top-1/2 left-0 w-1/6 shrink-0 rounded-[18%]\",\n        className\n      )}\n    >\n      {children}\n    </div>\n  ) : (\n    <svg\n      className={cn(\n        \"-translate-y-1/2 absolute top-1/2 left-0 w-1/6 shrink-0 rounded-[18%]\",\n        className\n      )}\n      enableBackground=\"new 0 0 110 92\"\n      viewBox=\"0 0 110 92\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <title>Chip</title>\n      <path\n        d=\"M1 13A12 12 0 0 1 13 1h84a12 12 0 0 1 12 12v66a12 12 0 0 1-12 12H13A12 12 0 0 1 1 79V13Z\"\n        fill=\"url(#kibo-credit-card-chip-gradient)\"\n      />\n      <path\n        d=\"M108 71.5H83.65L70.53 60.87A21.41 21.41 0 0 1 56 67.47V90h41a11 11 0 0 0 11-11v-7.5ZM76.48 47a21.38 21.38 0 0 1-4.63 12.36l12.5 10.14H108V47H76.48ZM2 69.5h24.14l12.02-10.12A21.38 21.38 0 0 1 33.52 47H2v22.5Zm53-43c-5.85 0-11.1 2.57-14.68 6.66A19.4 19.4 0 0 0 35.5 46a19.4 19.4 0 0 0 4.82 12.84A19.43 19.43 0 0 0 55 65.5c5.85 0 11.1-2.57 14.68-6.66A19.4 19.4 0 0 0 74.5 46a19.4 19.4 0 0 0-4.82-12.84A19.43 19.43 0 0 0 55 26.5Zm16.85 6.14A21.38 21.38 0 0 1 76.48 45H108V22.5H84.35l-12.5 10.14ZM2 45h31.52a21.38 21.38 0 0 1 4.64-12.38L26.14 22.5H2V45Zm0 34a11 11 0 0 0 11 11h41V67.47a21.41 21.41 0 0 1-14.52-6.59L27.14 71.26l-.27.24H2V79Zm106-66A11 11 0 0 0 97 2H56v22.52c5.7.27 10.83 2.74 14.53 6.61L83.65 20.5H108V13ZM2 20.5h24.87l.27.24 12.34 10.38A21.41 21.41 0 0 1 54 24.52V2H13A11 11 0 0 0 2 13v7.5ZM110 79a13 13 0 0 1-13 13H13A13 13 0 0 1 0 79V13A13 13 0 0 1 13 0h84a13 13 0 0 1 13 13v66Z\"\n        fill=\"#000\"\n      />\n      <defs>\n        <linearGradient\n          gradientUnits=\"userSpaceOnUse\"\n          id=\"kibo-credit-card-chip-gradient\"\n          x1=\"1\"\n          x2=\"112.7\"\n          y1=\"46\"\n          y2=\"78.12\"\n        >\n          <stop stopColor=\"#EDE5A6\" />\n          <stop offset=\"1\" stopColor=\"#CFA255\" />\n        </linearGradient>\n      </defs>\n    </svg>\n  );\n\nexport type CreditCardLogoProps = HTMLAttributes<HTMLDivElement>;\n\nexport const CreditCardLogo = ({\n  className,\n  ...props\n}: CreditCardLogoProps) => (\n  <div\n    className={cn(\"absolute top-0 right-0 size-1/6\", className)}\n    {...props}\n  />\n);\n\nexport type CreditCardFrontProps = HTMLAttributes<HTMLDivElement> & {\n  safeArea?: number;\n};\n\nexport const CreditCardFront = ({\n  className,\n  safeArea = 20,\n  children,\n  ...props\n}: CreditCardFrontProps) => (\n  <div\n    className={cn(\n      \"backface-hidden absolute inset-0 flex overflow-hidden bg-foreground/90\",\n      \"@xs:rounded-2xl rounded-lg\",\n      className\n    )}\n    {...props}\n  >\n    <div\n      className=\"relative flex-1\"\n      style={{\n        margin: `${safeArea}px`,\n      }}\n    >\n      {children}\n    </div>\n  </div>\n);\n\nexport type CreditCardServiceProviderProps = ComponentProps<typeof PaymentIcon>;\n\nexport const CreditCardServiceProvider = ({\n  className,\n  children,\n  type = \"Visa\",\n  ...props\n}: CreditCardServiceProviderProps) => {\n  if (children) {\n    return (\n      <div\n        className={cn(\n          \"absolute right-0 bottom-0\",\n          \"max-h-1/3 max-w-1/3\",\n          className\n        )}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  return (\n    <PaymentIcon\n      className={cn(\n        \"absolute right-0 bottom-0\",\n        \"max-h-1/3 max-w-1/3\",\n        className\n      )}\n      type={type}\n      {...props}\n    />\n  );\n};\n\nexport type CreditCardMagStripeProps = HTMLAttributes<HTMLDivElement>;\n\nexport type CreditCardBackContextValue = {\n  safeArea: number;\n};\n\nconst CreditCardBackContext = createContext<CreditCardBackContextValue>({\n  safeArea: 20,\n});\n\nexport type CreditCardBackProps = HTMLAttributes<HTMLDivElement> & {\n  safeArea?: number;\n};\n\nexport const CreditCardBack = ({\n  safeArea = 16,\n  children,\n  className,\n  ...props\n}: CreditCardBackProps) => {\n  const isInsideFlipper = useContext(CreditCardFlipContext);\n\n  return (\n    <CreditCardBackContext.Provider value={{ safeArea }}>\n      <div\n        className={cn(\n          \"backface-hidden absolute inset-0 flex overflow-hidden bg-foreground/90\",\n          \"@xs:rounded-2xl rounded-lg\",\n          isInsideFlipper && \"rotate-y-180\",\n          className\n        )}\n        {...props}\n      >\n        <div\n          className=\"relative flex-1\"\n          style={{\n            margin: `${safeArea}px`,\n          }}\n        >\n          {children}\n        </div>\n      </div>\n    </CreditCardBackContext.Provider>\n  );\n};\n\nexport const CreditCardMagStripe = ({\n  className,\n  ...props\n}: CreditCardMagStripeProps) => {\n  const context = useContext(CreditCardBackContext);\n\n  return (\n    <div\n      className={cn(\n        \"-translate-x-1/2 absolute top-[3%] left-1/2 h-1/4 bg-gray-900\",\n        className\n      )}\n      style={{\n        width: `calc(100% + 2 * ${context.safeArea}px)`,\n      }}\n      {...props}\n    />\n  );\n};\n\nexport type CreditCardNumberProps = HTMLAttributes<HTMLParagraphElement>;\n\nexport const CreditCardNumber = ({\n  className,\n  children,\n  style,\n  ...props\n}: CreditCardNumberProps) => (\n  <p\n    className={cn(\"font-mono\", \"@xs:text-2xl\", className)}\n    style={{\n      lineHeight: \"100%\",\n      ...style,\n    }}\n    {...props}\n  >\n    {children}\n  </p>\n);\n\nexport type CreditCardExpiryProps = HTMLAttributes<HTMLParagraphElement>;\n\nexport const CreditCardExpiry = ({\n  className,\n  style,\n  ...props\n}: CreditCardExpiryProps) => (\n  <p\n    className={cn(\"font-mono\", className)}\n    style={{\n      lineHeight: \"100%\",\n      ...style,\n    }}\n    {...props}\n  />\n);\n\nexport type CreditCardCvvProps = HTMLAttributes<HTMLParagraphElement>;\n\nexport const CreditCardCvv = ({\n  className,\n  style,\n  ...props\n}: CreditCardCvvProps) => (\n  <p\n    className={cn(\"font-mono\", className)}\n    style={{\n      lineHeight: \"100%\",\n      ...style,\n    }}\n    {...props}\n  />\n);\n","target":"components/kibo-ui/credit-card/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"cursor","type":"registry:ui","title":"cursor","description":"A cursor component, great for realtime interactive applications.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":[],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"import { Children, type HTMLAttributes, type SVGProps } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type CursorProps = HTMLAttributes<HTMLSpanElement>;\n\nexport const Cursor = ({ className, children, ...props }: CursorProps) => (\n  <span\n    className={cn(\"pointer-events-none relative select-none\", className)}\n    {...props}\n  >\n    {children}\n  </span>\n);\n\nexport type CursorPointerProps = SVGProps<SVGSVGElement>;\n\nexport const CursorPointer = ({ className, ...props }: CursorPointerProps) => (\n  <svg\n    aria-hidden=\"true\"\n    className={cn(\"size-3.5\", className)}\n    fill=\"none\"\n    focusable=\"false\"\n    height=\"20\"\n    viewBox=\"0 0 20 20\"\n    width=\"20\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    {...props}\n  >\n    <path\n      d=\"M19.438 6.716 1.115.05A.832.832 0 0 0 .05 1.116L6.712 19.45a.834.834 0 0 0 1.557.025l3.198-8 7.995-3.2a.833.833 0 0 0 0-1.559h-.024Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport type CursorBodyProps = HTMLAttributes<HTMLSpanElement>;\n\nexport const CursorBody = ({\n  children,\n  className,\n  ...props\n}: CursorBodyProps) => (\n  <span\n    className={cn(\n      \"relative ml-3.5 flex flex-col whitespace-nowrap rounded-xl py-1 pr-3 pl-2.5 text-xs\",\n      Children.count(children) > 1 && \"rounded-tl [&>:first-child]:opacity-70\",\n      \"bg-secondary text-foreground\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n  </span>\n);\n\nexport type CursorNameProps = HTMLAttributes<HTMLSpanElement>;\n\nexport const CursorName = (props: CursorNameProps) => <span {...props} />;\n\nexport type CursorMessageProps = HTMLAttributes<HTMLSpanElement>;\n\nexport const CursorMessage = (props: CursorMessageProps) => <span {...props} />;\n","target":"components/kibo-ui/cursor/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"deck","type":"registry:ui","title":"deck","description":"A Tinder-like swipeable card stack component with smooth animations.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@radix-ui/react-use-controllable-state","lucide-react","motion"],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport {\n  motion,\n  type PanInfo,\n  useMotionValue,\n  useTransform,\n} from \"motion/react\";\nimport {\n  Children,\n  cloneElement,\n  type HTMLAttributes,\n  type ReactElement,\n  useCallback,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type DeckProps = HTMLAttributes<HTMLDivElement>;\n\nexport const Deck = ({ className, ...props }: DeckProps) => (\n  <div className={cn(\"relative isolate\", className)} {...props} />\n);\n\nexport type DeckCardsProps = HTMLAttributes<HTMLDivElement> & {\n  onSwipe?: (index: number, direction: \"left\" | \"right\") => void;\n  onSwipeEnd?: (index: number, direction: \"left\" | \"right\") => void;\n  threshold?: number;\n  stackSize?: number;\n  perspective?: number;\n  scale?: number;\n  currentIndex?: number;\n  defaultCurrentIndex?: number;\n  onCurrentIndexChange?: (index: number) => void;\n  animateOnIndexChange?: boolean;\n  indexChangeDirection?: \"left\" | \"right\";\n};\n\nexport const DeckCards = ({\n  children,\n  className,\n  onSwipe,\n  onSwipeEnd,\n  threshold = 150,\n  stackSize = 3,\n  perspective = 1000,\n  scale = 0.05,\n  currentIndex: currentIndexProp,\n  defaultCurrentIndex = 0,\n  onCurrentIndexChange,\n  animateOnIndexChange = true,\n  indexChangeDirection = \"left\",\n  ...props\n}: DeckCardsProps) => {\n  const childrenArray = Children.toArray(children) as ReactElement[];\n  const [currentIndex, setCurrentIndex] = useControllableState({\n    prop: currentIndexProp,\n    defaultProp: defaultCurrentIndex,\n    onChange: onCurrentIndexChange,\n  });\n  const [exitDirection, setExitDirection] = useState<\"left\" | \"right\" | null>(\n    null\n  );\n  const [displayIndex, setDisplayIndex] = useState(currentIndex);\n  const isInternalChangeRef = useRef(false);\n  const prevIndexRef = useRef(currentIndex);\n\n  // Detect external currentIndex changes and trigger animation\n  useEffect(() => {\n    const prevIndex = prevIndexRef.current;\n\n    // Skip initial mount and internal changes\n    if (prevIndex === currentIndex || isInternalChangeRef.current) {\n      isInternalChangeRef.current = false;\n      prevIndexRef.current = currentIndex;\n      setDisplayIndex(currentIndex);\n      return;\n    }\n\n    // Only animate if the option is enabled and we have cards to show\n    if (animateOnIndexChange && prevIndex < childrenArray.length) {\n      setExitDirection(indexChangeDirection);\n\n      // Update display index after animation completes\n      setTimeout(() => {\n        setExitDirection(null);\n        setDisplayIndex(currentIndex);\n      }, 300);\n    } else {\n      // No animation, update display index immediately\n      setDisplayIndex(currentIndex);\n    }\n\n    prevIndexRef.current = currentIndex;\n  }, [\n    currentIndex,\n    animateOnIndexChange,\n    indexChangeDirection,\n    childrenArray.length,\n  ]);\n\n  const handleSwipe = useCallback(\n    (direction: \"left\" | \"right\") => {\n      if (displayIndex >= childrenArray.length) {\n        return;\n      }\n\n      setExitDirection(direction);\n\n      if (direction === \"left\") {\n        onSwipe?.(displayIndex, \"left\");\n      } else {\n        onSwipe?.(displayIndex, \"right\");\n      }\n\n      onSwipeEnd?.(displayIndex, direction);\n\n      // Move to next card after animation\n      setTimeout(() => {\n        isInternalChangeRef.current = true;\n        const newIndex = displayIndex + 1;\n        setCurrentIndex(newIndex);\n        setDisplayIndex(newIndex);\n        setExitDirection(null);\n      }, 300);\n    },\n    [displayIndex, childrenArray.length, onSwipe, onSwipeEnd, setCurrentIndex]\n  );\n\n  const visibleCards = childrenArray.slice(\n    displayIndex,\n    displayIndex + stackSize\n  );\n\n  if (displayIndex >= childrenArray.length) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\"relative z-10 size-full\", className)}\n      style={{ perspective }}\n      {...props}\n    >\n      {visibleCards.map((child, index) => {\n        const isTopCard = !index;\n        const zIndex = stackSize - index;\n        const scaleValue = 1 - index * scale;\n        const yOffset = index * 4;\n        const cardKey = `${displayIndex}-${child.key ?? index}`;\n\n        if (isTopCard) {\n          return (\n            <DeckCard\n              exitDirection={exitDirection}\n              key={cardKey}\n              onSwipe={handleSwipe}\n              style={{\n                zIndex,\n                scale: scaleValue,\n                y: yOffset,\n              }}\n              threshold={threshold}\n            >\n              {child}\n            </DeckCard>\n          );\n        }\n\n        const nextCardScale = index === 1 && exitDirection ? 1 : scaleValue;\n        const nextCardY = index === 1 && exitDirection ? 0 : yOffset;\n\n        return (\n          <motion.div\n            animate={{\n              scale: nextCardScale,\n              y: nextCardY,\n            }}\n            className=\"absolute inset-0\"\n            key={cardKey}\n            style={{\n              zIndex,\n              scale: scaleValue,\n              y: yOffset,\n            }}\n            transition={{ duration: 0.3, ease: \"easeOut\" }}\n          >\n            {child}\n          </motion.div>\n        );\n      })}\n    </div>\n  );\n};\n\ntype DeckCardProps = {\n  children: ReactElement;\n  onSwipe: (direction: \"left\" | \"right\") => void;\n  threshold: number;\n  style?: object;\n  exitDirection: \"left\" | \"right\" | null;\n};\n\nconst DeckCard = ({\n  children,\n  onSwipe,\n  threshold,\n  style,\n  exitDirection,\n}: DeckCardProps) => {\n  const x = useMotionValue(0);\n  const rotate = useTransform(x, [-200, 200], [-25, 25]);\n  const opacity = useTransform(\n    x,\n    [-200, -threshold, 0, threshold, 200],\n    [0, 1, 1, 1, 0]\n  );\n\n  const handleDragEnd = (_: unknown, info: PanInfo) => {\n    const swipeThreshold = threshold;\n\n    if (Math.abs(info.offset.x) > swipeThreshold) {\n      const direction = info.offset.x > 0 ? \"right\" : \"left\";\n      onSwipe(direction);\n    }\n  };\n\n  let exitX = 0;\n\n  if (exitDirection === \"left\") {\n    exitX = -500;\n  } else if (exitDirection === \"right\") {\n    exitX = 500;\n  }\n\n  const castedChildren = children as ReactElement<\n    HTMLAttributes<HTMLDivElement>\n  >;\n\n  return (\n    <motion.div\n      animate={exitDirection ? { x: exitX, opacity: 0 } : undefined}\n      className=\"absolute inset-0 cursor-grab active:cursor-grabbing\"\n      drag=\"x\"\n      dragConstraints={{ left: 0, right: 0 }}\n      onDragEnd={handleDragEnd}\n      style={{\n        x,\n        rotate,\n        opacity,\n        ...style,\n      }}\n      transition={{ duration: 0.3, ease: \"easeOut\" }}\n      whileDrag={{ scale: 1.05 }}\n    >\n      {cloneElement(castedChildren, {\n        className: cn(\n          \"h-full w-full select-none rounded-lg shadow-lg\",\n          castedChildren.props.className\n        ),\n      })}\n    </motion.div>\n  );\n};\n\nexport type DeckItemProps = HTMLAttributes<HTMLDivElement>;\n\nexport const DeckItem = ({ className, ...props }: DeckItemProps) => (\n  <div\n    className={cn(\n      \"flex h-full w-full items-center justify-center rounded-lg border bg-card text-card-foreground shadow-lg\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type DeckEmptyProps = HTMLAttributes<HTMLDivElement>;\n\nexport const DeckEmpty = ({\n  children,\n  className,\n  ...props\n}: HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"absolute inset-0 flex items-center justify-center rounded-lg border border-dashed text-muted-foreground\",\n      className\n    )}\n    {...props}\n  >\n    {children ?? <p className=\"text-sm\">No more cards</p>}\n  </div>\n);\n","target":"components/kibo-ui/deck/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"dialog-stack","type":"registry:ui","title":"dialog-stack","description":"Composable stacked dialogs, useful for creating a wizard, nested form or multi-step process. It provides a consistent layout and styling for each dialog, and includes navigation component.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@radix-ui/react-use-controllable-state","radix-ui"],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport { Portal } from \"radix-ui\";\nimport type {\n  ButtonHTMLAttributes,\n  Dispatch,\n  HTMLAttributes,\n  MouseEvent,\n  MouseEventHandler,\n  ReactElement,\n  SetStateAction,\n} from \"react\";\nimport {\n  Children,\n  cloneElement,\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useState,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ntype DialogStackContextType = {\n  activeIndex: number;\n  setActiveIndex: Dispatch<SetStateAction<number>>;\n  totalDialogs: number;\n  setTotalDialogs: Dispatch<SetStateAction<number>>;\n  isOpen: boolean;\n  setIsOpen: Dispatch<SetStateAction<boolean>>;\n  clickable: boolean;\n};\n\nconst DialogStackContext = createContext<DialogStackContextType>({\n  activeIndex: 0,\n  setActiveIndex: () => {},\n  totalDialogs: 0,\n  setTotalDialogs: () => {},\n  isOpen: false,\n  setIsOpen: () => {},\n  clickable: false,\n});\n\ntype DialogStackChildProps = {\n  index?: number;\n};\n\nexport type DialogStackProps = HTMLAttributes<HTMLDivElement> & {\n  open?: boolean;\n  clickable?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  defaultOpen?: boolean;\n};\n\nexport const DialogStack = ({\n  children,\n  className,\n  open,\n  defaultOpen = false,\n  onOpenChange,\n  clickable = false,\n  ...props\n}: DialogStackProps) => {\n  const [activeIndex, setActiveIndex] = useState(0);\n  const [isOpen, setIsOpen] = useControllableState({\n    defaultProp: defaultOpen,\n    prop: open,\n    onChange: onOpenChange,\n  });\n\n  useEffect(() => {\n    if (onOpenChange && isOpen !== undefined) {\n      onOpenChange(isOpen);\n    }\n  }, [isOpen, onOpenChange]);\n\n  return (\n    <DialogStackContext.Provider\n      value={{\n        activeIndex,\n        setActiveIndex,\n        totalDialogs: 0,\n        setTotalDialogs: () => {},\n        isOpen: isOpen ?? false,\n        setIsOpen: (value) => setIsOpen(Boolean(value)),\n        clickable,\n      }}\n    >\n      <div className={className} {...props}>\n        {children}\n      </div>\n    </DialogStackContext.Provider>\n  );\n};\n\nexport type DialogStackTriggerProps =\n  ButtonHTMLAttributes<HTMLButtonElement> & {\n    asChild?: boolean;\n  };\n\nexport const DialogStackTrigger = ({\n  children,\n  className,\n  onClick,\n  asChild,\n  ...props\n}: DialogStackTriggerProps) => {\n  const context = useContext(DialogStackContext);\n\n  if (!context) {\n    throw new Error(\"DialogStackTrigger must be used within a DialogStack\");\n  }\n\n  const handleClick: MouseEventHandler<HTMLButtonElement> = (e) => {\n    context.setIsOpen(true);\n    onClick?.(e);\n  };\n\n  if (asChild && children) {\n    const child = children as ReactElement<{\n      onClick: MouseEventHandler<HTMLButtonElement>;\n      className?: string;\n    }>;\n    return cloneElement(child, {\n      onClick: (e: MouseEvent<HTMLButtonElement>) => {\n        handleClick(e);\n        child.props.onClick?.(e);\n      },\n      className: cn(className, child.props.className),\n      ...props,\n    });\n  }\n\n  return (\n    <button\n      className={cn(\n        \"inline-flex items-center justify-center whitespace-nowrap rounded-md font-medium text-sm\",\n        \"ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2\",\n        \"focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\",\n        \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        \"h-10 px-4 py-2\",\n        className\n      )}\n      onClick={handleClick}\n      {...props}\n    >\n      {children}\n    </button>\n  );\n};\n\nexport type DialogStackOverlayProps = HTMLAttributes<HTMLDivElement>;\n\nexport const DialogStackOverlay = ({\n  className,\n  ...props\n}: DialogStackOverlayProps) => {\n  const context = useContext(DialogStackContext);\n\n  if (!context) {\n    throw new Error(\"DialogStackOverlay must be used within a DialogStack\");\n  }\n\n  const handleClick = useCallback(() => {\n    context.setIsOpen(false);\n  }, [context.setIsOpen]);\n\n  if (!context.isOpen) {\n    return null;\n  }\n\n  return (\n    // biome-ignore lint/a11y/noStaticElementInteractions: \"This is a clickable overlay\"\n    // biome-ignore lint/a11y/useKeyWithClickEvents: \"This is a clickable overlay\"\n    <div\n      className={cn(\n        \"fixed inset-0 z-50 bg-black/80\",\n        \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n        \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n        className\n      )}\n      onClick={handleClick}\n      {...props}\n    />\n  );\n};\n\nexport type DialogStackBodyProps = HTMLAttributes<HTMLDivElement> & {\n  children:\n    | ReactElement<DialogStackChildProps>[]\n    | ReactElement<DialogStackChildProps>;\n};\n\nexport const DialogStackBody = ({\n  children,\n  className,\n  ...props\n}: DialogStackBodyProps) => {\n  const context = useContext(DialogStackContext);\n  const [totalDialogs, setTotalDialogs] = useState(Children.count(children));\n\n  if (!context) {\n    throw new Error(\"DialogStackBody must be used within a DialogStack\");\n  }\n\n  if (!context.isOpen) {\n    return null;\n  }\n\n  return (\n    <DialogStackContext.Provider\n      value={{\n        ...context,\n        totalDialogs,\n        setTotalDialogs,\n      }}\n    >\n      <Portal.Root>\n        <div\n          className={cn(\n            \"pointer-events-none fixed inset-0 z-50 mx-auto flex w-full max-w-lg flex-col items-center justify-center\",\n            className\n          )}\n          {...props}\n        >\n          <div className=\"pointer-events-auto relative flex w-full flex-col items-center justify-center\">\n            {Children.map(children, (child, index) => {\n              const childElement = child as ReactElement<{\n                index: number;\n                onClick: MouseEventHandler<HTMLButtonElement>;\n                className?: string;\n              }>;\n\n              return cloneElement(childElement, {\n                ...childElement.props,\n                index,\n              });\n            })}\n          </div>\n        </div>\n      </Portal.Root>\n    </DialogStackContext.Provider>\n  );\n};\n\nexport type DialogStackContentProps = HTMLAttributes<HTMLDivElement> & {\n  index?: number;\n  offset?: number;\n};\n\nexport const DialogStackContent = ({\n  children,\n  className,\n  index = 0,\n  offset = 10,\n  ...props\n}: DialogStackContentProps) => {\n  const context = useContext(DialogStackContext);\n\n  if (!context) {\n    throw new Error(\"DialogStackContent must be used within a DialogStack\");\n  }\n\n  if (!context.isOpen) {\n    return null;\n  }\n\n  const handleClick = () => {\n    if (context.clickable && context.activeIndex > index) {\n      context.setActiveIndex(index ?? 0);\n    }\n  };\n\n  const distanceFromActive = index - context.activeIndex;\n  const translateY =\n    distanceFromActive < 0\n      ? `-${Math.abs(distanceFromActive) * offset}px`\n      : `${Math.abs(distanceFromActive) * offset}px`;\n\n  return (\n    // biome-ignore lint/a11y/noStaticElementInteractions: \"This is a clickable dialog\"\n    // biome-ignore lint/a11y/useKeyWithClickEvents: \"This is a clickable dialog\"\n    <div\n      className={cn(\n        \"h-auto w-full rounded-lg border bg-background p-6 shadow-lg transition-all duration-300\",\n        className\n      )}\n      onClick={handleClick}\n      style={{\n        top: 0,\n        transform: `translateY(${translateY})`,\n        width: `calc(100% - ${Math.abs(distanceFromActive) * 10}px)`,\n        zIndex: 50 - Math.abs(context.activeIndex - (index ?? 0)),\n        position: distanceFromActive ? \"absolute\" : \"relative\",\n        opacity: distanceFromActive > 0 ? 0 : 1,\n        cursor:\n          context.clickable && context.activeIndex > index\n            ? \"pointer\"\n            : \"default\",\n      }}\n      {...props}\n    >\n      <div\n        className={cn(\n          \"h-full w-full transition-all duration-300\",\n          context.activeIndex !== index &&\n            \"pointer-events-none select-none opacity-0\"\n        )}\n      >\n        {children}\n      </div>\n    </div>\n  );\n};\n\nexport type DialogStackTitleProps = HTMLAttributes<HTMLHeadingElement>;\n\nexport const DialogStackTitle = ({\n  children,\n  className,\n  ...props\n}: DialogStackTitleProps) => (\n  <h2\n    className={cn(\n      \"font-semibold text-lg leading-none tracking-tight\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n  </h2>\n);\n\nexport type DialogStackDescriptionProps = HTMLAttributes<HTMLParagraphElement>;\n\nexport const DialogStackDescription = ({\n  children,\n  className,\n  ...props\n}: DialogStackDescriptionProps) => (\n  <p className={cn(\"text-muted-foreground text-sm\", className)} {...props}>\n    {children}\n  </p>\n);\n\nexport type DialogStackHeaderProps = HTMLAttributes<HTMLDivElement>;\n\nexport const DialogStackHeader = ({\n  className,\n  ...props\n}: DialogStackHeaderProps) => (\n  <div\n    className={cn(\n      \"flex flex-col space-y-1.5 text-center sm:text-left\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type DialogStackFooterProps = HTMLAttributes<HTMLDivElement>;\n\nexport const DialogStackFooter = ({\n  children,\n  className,\n  ...props\n}: DialogStackFooterProps) => (\n  <div\n    className={cn(\"flex items-center justify-end space-x-2 pt-4\", className)}\n    {...props}\n  >\n    {children}\n  </div>\n);\n\nexport type DialogStackNextProps = ButtonHTMLAttributes<HTMLButtonElement> & {\n  asChild?: boolean;\n};\n\nexport const DialogStackNext = ({\n  children,\n  className,\n  asChild,\n  ...props\n}: DialogStackNextProps) => {\n  const context = useContext(DialogStackContext);\n\n  if (!context) {\n    throw new Error(\"DialogStackNext must be used within a DialogStack\");\n  }\n\n  const handleNext = () => {\n    if (context.activeIndex < context.totalDialogs - 1) {\n      context.setActiveIndex(context.activeIndex + 1);\n    }\n  };\n\n  if (asChild && children) {\n    const child = children as ReactElement<{\n      onClick: MouseEventHandler<HTMLButtonElement>;\n      className?: string;\n    }>;\n\n    return cloneElement(child, {\n      onClick: (e: MouseEvent<HTMLButtonElement>) => {\n        handleNext();\n        child.props.onClick?.(e);\n      },\n      className: cn(className, child.props.className),\n      ...props,\n    });\n  }\n\n  return (\n    <button\n      className={cn(\n        \"inline-flex items-center justify-center whitespace-nowrap rounded-md font-medium text-sm ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\",\n        className\n      )}\n      disabled={context.activeIndex >= context.totalDialogs - 1}\n      onClick={handleNext}\n      type=\"button\"\n      {...props}\n    >\n      {children || \"Next\"}\n    </button>\n  );\n};\n\nexport type DialogStackPreviousProps =\n  ButtonHTMLAttributes<HTMLButtonElement> & {\n    asChild?: boolean;\n  };\n\nexport const DialogStackPrevious = ({\n  children,\n  className,\n  asChild,\n  ...props\n}: DialogStackPreviousProps) => {\n  const context = useContext(DialogStackContext);\n\n  if (!context) {\n    throw new Error(\"DialogStackPrevious must be used within a DialogStack\");\n  }\n\n  const handlePrevious = () => {\n    if (context.activeIndex > 0) {\n      context.setActiveIndex(context.activeIndex - 1);\n    }\n  };\n\n  if (asChild && children) {\n    const child = children as ReactElement<{\n      onClick: MouseEventHandler<HTMLButtonElement>;\n      className?: string;\n    }>;\n\n    return cloneElement(child, {\n      onClick: (e: MouseEvent<HTMLButtonElement>) => {\n        handlePrevious();\n        child.props.onClick?.(e);\n      },\n      className: cn(className, child.props.className),\n      ...props,\n    });\n  }\n\n  return (\n    <button\n      className={cn(\n        \"inline-flex items-center justify-center whitespace-nowrap rounded-md font-medium text-sm ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\",\n        className\n      )}\n      disabled={context.activeIndex <= 0}\n      onClick={handlePrevious}\n      type=\"button\"\n      {...props}\n    >\n      {children || \"Previous\"}\n    </button>\n  );\n};\n","target":"components/kibo-ui/dialog-stack/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"dropzone","type":"registry:ui","title":"dropzone","description":"Allows users to drag-and-drop files into a container to upload or process them.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["lucide-react","react-dropzone"],"devDependencies":[],"registryDependencies":["button"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { UploadIcon } from \"lucide-react\";\nimport type { ReactNode } from \"react\";\nimport { createContext, useContext } from \"react\";\nimport type { DropEvent, DropzoneOptions, FileRejection } from \"react-dropzone\";\nimport { useDropzone } from \"react-dropzone\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\ntype DropzoneContextType = {\n  src?: File[];\n  accept?: DropzoneOptions[\"accept\"];\n  maxSize?: DropzoneOptions[\"maxSize\"];\n  minSize?: DropzoneOptions[\"minSize\"];\n  maxFiles?: DropzoneOptions[\"maxFiles\"];\n};\n\nconst renderBytes = (bytes: number) => {\n  const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\"];\n  let size = bytes;\n  let unitIndex = 0;\n\n  while (size >= 1024 && unitIndex < units.length - 1) {\n    size /= 1024;\n    unitIndex++;\n  }\n\n  return `${size.toFixed(2)}${units[unitIndex]}`;\n};\n\nconst DropzoneContext = createContext<DropzoneContextType | undefined>(\n  undefined\n);\n\nexport type DropzoneProps = Omit<DropzoneOptions, \"onDrop\"> & {\n  src?: File[];\n  className?: string;\n  onDrop?: (\n    acceptedFiles: File[],\n    fileRejections: FileRejection[],\n    event: DropEvent\n  ) => void;\n  children?: ReactNode;\n};\n\nexport const Dropzone = ({\n  accept,\n  maxFiles = 1,\n  maxSize,\n  minSize,\n  onDrop,\n  onError,\n  disabled,\n  src,\n  className,\n  children,\n  ...props\n}: DropzoneProps) => {\n  const { getRootProps, getInputProps, isDragActive } = useDropzone({\n    accept,\n    maxFiles,\n    maxSize,\n    minSize,\n    onError,\n    disabled,\n    onDrop: (acceptedFiles, fileRejections, event) => {\n      if (fileRejections.length > 0) {\n        const message = fileRejections.at(0)?.errors.at(0)?.message;\n        onError?.(new Error(message));\n        return;\n      }\n\n      onDrop?.(acceptedFiles, fileRejections, event);\n    },\n    ...props,\n  });\n\n  return (\n    <DropzoneContext.Provider\n      key={JSON.stringify(src)}\n      value={{ src, accept, maxSize, minSize, maxFiles }}\n    >\n      <Button\n        className={cn(\n          \"relative h-auto w-full flex-col overflow-hidden p-8\",\n          isDragActive && \"outline-none ring-1 ring-ring\",\n          className\n        )}\n        disabled={disabled}\n        type=\"button\"\n        variant=\"outline\"\n        {...getRootProps()}\n      >\n        <input {...getInputProps()} disabled={disabled} />\n        {children}\n      </Button>\n    </DropzoneContext.Provider>\n  );\n};\n\nconst useDropzoneContext = () => {\n  const context = useContext(DropzoneContext);\n\n  if (!context) {\n    throw new Error(\"useDropzoneContext must be used within a Dropzone\");\n  }\n\n  return context;\n};\n\nexport type DropzoneContentProps = {\n  children?: ReactNode;\n  className?: string;\n};\n\nconst maxLabelItems = 3;\n\nexport const DropzoneContent = ({\n  children,\n  className,\n}: DropzoneContentProps) => {\n  const { src } = useDropzoneContext();\n\n  if (!src) {\n    return null;\n  }\n\n  if (children) {\n    return children;\n  }\n\n  return (\n    <div className={cn(\"flex flex-col items-center justify-center\", className)}>\n      <div className=\"flex size-8 items-center justify-center rounded-md bg-muted text-muted-foreground\">\n        <UploadIcon size={16} />\n      </div>\n      <p className=\"my-2 w-full truncate font-medium text-sm\">\n        {src.length > maxLabelItems\n          ? `${new Intl.ListFormat(\"en\").format(\n              src.slice(0, maxLabelItems).map((file) => file.name)\n            )} and ${src.length - maxLabelItems} more`\n          : new Intl.ListFormat(\"en\").format(src.map((file) => file.name))}\n      </p>\n      <p className=\"w-full text-wrap text-muted-foreground text-xs\">\n        Drag and drop or click to replace\n      </p>\n    </div>\n  );\n};\n\nexport type DropzoneEmptyStateProps = {\n  children?: ReactNode;\n  className?: string;\n};\n\nexport const DropzoneEmptyState = ({\n  children,\n  className,\n}: DropzoneEmptyStateProps) => {\n  const { src, accept, maxSize, minSize, maxFiles } = useDropzoneContext();\n\n  if (src) {\n    return null;\n  }\n\n  if (children) {\n    return children;\n  }\n\n  let caption = \"\";\n\n  if (accept) {\n    caption += \"Accepts \";\n    caption += new Intl.ListFormat(\"en\").format(Object.keys(accept));\n  }\n\n  if (minSize && maxSize) {\n    caption += ` between ${renderBytes(minSize)} and ${renderBytes(maxSize)}`;\n  } else if (minSize) {\n    caption += ` at least ${renderBytes(minSize)}`;\n  } else if (maxSize) {\n    caption += ` less than ${renderBytes(maxSize)}`;\n  }\n\n  return (\n    <div className={cn(\"flex flex-col items-center justify-center\", className)}>\n      <div className=\"flex size-8 items-center justify-center rounded-md bg-muted text-muted-foreground\">\n        <UploadIcon size={16} />\n      </div>\n      <p className=\"my-2 w-full truncate text-wrap font-medium text-sm\">\n        Upload {maxFiles === 1 ? \"a file\" : \"files\"}\n      </p>\n      <p className=\"w-full truncate text-wrap text-muted-foreground text-xs\">\n        Drag and drop or click to upload\n      </p>\n      {caption && (\n        <p className=\"text-wrap text-muted-foreground text-xs\">{caption}.</p>\n      )}\n    </div>\n  );\n};\n","target":"components/kibo-ui/dropzone/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"editor","type":"registry:ui","title":"editor","description":"The Editor component is a powerful and flexible text editor that allows you to create and edit rich text content.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@floating-ui/dom","@tiptap/core","@tiptap/extension-code-block-lowlight","@tiptap/extension-list","@tiptap/extension-subscript","@tiptap/extension-superscript","@tiptap/extension-table","@tiptap/extension-text-style","@tiptap/extension-typography","@tiptap/extensions","@tiptap/pm","@tiptap/react","@tiptap/starter-kit","@tiptap/suggestion","fuse.js","lowlight","lucide-react","tippy.js"],"devDependencies":[],"registryDependencies":["button","command","dropdown-menu","popover","separator","tooltip"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport type { Editor, Range } from \"@tiptap/core\";\nimport { mergeAttributes, Node } from \"@tiptap/core\";\nimport CodeBlockLowlight from \"@tiptap/extension-code-block-lowlight\";\nimport { TaskItem, TaskList } from \"@tiptap/extension-list\";\nimport Subscript from \"@tiptap/extension-subscript\";\nimport Superscript from \"@tiptap/extension-superscript\";\nimport {\n  Table,\n  TableCell,\n  TableHeader,\n  TableRow,\n} from \"@tiptap/extension-table\";\nimport { TextStyleKit } from \"@tiptap/extension-text-style\";\nimport Typography from \"@tiptap/extension-typography\";\nimport { CharacterCount, Placeholder } from \"@tiptap/extensions\";\nimport type { DOMOutputSpec, Node as ProseMirrorNode } from \"@tiptap/pm/model\";\nimport { PluginKey } from \"@tiptap/pm/state\";\nimport {\n  ReactRenderer,\n  EditorProvider as TiptapEditorProvider,\n  type EditorProviderProps as TiptapEditorProviderProps,\n  useCurrentEditor,\n} from \"@tiptap/react\";\nimport {\n  BubbleMenu,\n  type BubbleMenuProps,\n  FloatingMenu,\n  type FloatingMenuProps,\n} from \"@tiptap/react/menus\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandItem,\n  CommandList,\n} from \"@/components/ui/command\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { Separator } from \"@/components/ui/separator\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\n\nexport type { Editor, JSONContent } from \"@tiptap/react\";\n\nimport StarterKit from \"@tiptap/starter-kit\";\nimport Suggestion, { type SuggestionOptions } from \"@tiptap/suggestion\";\nimport Fuse from \"fuse.js\";\nimport { all, createLowlight } from \"lowlight\";\nimport {\n  ArrowDownIcon,\n  ArrowLeftIcon,\n  ArrowRightIcon,\n  ArrowUpIcon,\n  BoldIcon,\n  BoltIcon,\n  CheckIcon,\n  CheckSquareIcon,\n  ChevronDownIcon,\n  CodeIcon,\n  ColumnsIcon,\n  EllipsisIcon,\n  EllipsisVerticalIcon,\n  ExternalLinkIcon,\n  Heading1Icon,\n  Heading2Icon,\n  Heading3Icon,\n  ItalicIcon,\n  ListIcon,\n  ListOrderedIcon,\n  type LucideIcon,\n  type LucideProps,\n  RemoveFormattingIcon,\n  RowsIcon,\n  StrikethroughIcon,\n  SubscriptIcon,\n  SuperscriptIcon,\n  TableCellsMergeIcon,\n  TableColumnsSplitIcon,\n  TableIcon,\n  TextIcon,\n  TextQuoteIcon,\n  TrashIcon,\n  UnderlineIcon,\n} from \"lucide-react\";\nimport type { FormEventHandler, HTMLAttributes, ReactNode } from \"react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport tippy, { type Instance as TippyInstance } from \"tippy.js\";\n\ntype SlashNodeAttrs = {\n  id: string | null;\n  label?: string | null;\n};\n\ntype SlashOptions<\n  SlashOptionSuggestionItem = unknown,\n  Attrs = SlashNodeAttrs,\n> = {\n  HTMLAttributes: Record<string, unknown>;\n  renderText: (props: {\n    options: SlashOptions<SlashOptionSuggestionItem, Attrs>;\n    node: ProseMirrorNode;\n  }) => string;\n  renderHTML: (props: {\n    options: SlashOptions<SlashOptionSuggestionItem, Attrs>;\n    node: ProseMirrorNode;\n  }) => DOMOutputSpec;\n  deleteTriggerWithBackspace: boolean;\n  suggestion: Omit<\n    SuggestionOptions<SlashOptionSuggestionItem, Attrs>,\n    \"editor\"\n  >;\n};\n\nconst SlashPluginKey = new PluginKey(\"slash\");\n\nexport type SuggestionItem = {\n  title: string;\n  description: string;\n  icon: LucideIcon;\n  searchTerms: string[];\n  command: (props: { editor: Editor; range: Range }) => void;\n};\n\nexport const defaultSlashSuggestions: SuggestionOptions<SuggestionItem>[\"items\"] =\n  () => [\n    {\n      title: \"Text\",\n      description: \"Just start typing with plain text.\",\n      searchTerms: [\"p\", \"paragraph\"],\n      icon: TextIcon,\n      command: ({ editor, range }) => {\n        editor\n          .chain()\n          .focus()\n          .deleteRange(range)\n          .toggleNode(\"paragraph\", \"paragraph\")\n          .run();\n      },\n    },\n    {\n      title: \"To-do List\",\n      description: \"Track tasks with a to-do list.\",\n      searchTerms: [\"todo\", \"task\", \"list\", \"check\", \"checkbox\"],\n      icon: CheckSquareIcon,\n      command: ({ editor, range }) => {\n        editor\n          .chain()\n          .focus()\n          .deleteRange(range)\n          .toggleList(\"taskList\", \"taskItem\")\n          .run();\n      },\n    },\n    {\n      title: \"Heading 1\",\n      description: \"Big section heading.\",\n      searchTerms: [\"title\", \"big\", \"large\"],\n      icon: Heading1Icon,\n      command: ({ editor, range }) => {\n        editor\n          .chain()\n          .focus()\n          .deleteRange(range)\n          .setNode(\"heading\", { level: 1 })\n          .run();\n      },\n    },\n    {\n      title: \"Heading 2\",\n      description: \"Medium section heading.\",\n      searchTerms: [\"subtitle\", \"medium\"],\n      icon: Heading2Icon,\n      command: ({ editor, range }) => {\n        editor\n          .chain()\n          .focus()\n          .deleteRange(range)\n          .setNode(\"heading\", { level: 2 })\n          .run();\n      },\n    },\n    {\n      title: \"Heading 3\",\n      description: \"Small section heading.\",\n      searchTerms: [\"subtitle\", \"small\"],\n      icon: Heading3Icon,\n      command: ({ editor, range }) => {\n        editor\n          .chain()\n          .focus()\n          .deleteRange(range)\n          .setNode(\"heading\", { level: 3 })\n          .run();\n      },\n    },\n    {\n      title: \"Bullet List\",\n      description: \"Create a simple bullet list.\",\n      searchTerms: [\"unordered\", \"point\"],\n      icon: ListIcon,\n      command: ({ editor, range }) => {\n        editor.chain().focus().deleteRange(range).toggleBulletList().run();\n      },\n    },\n    {\n      title: \"Numbered List\",\n      description: \"Create a list with numbering.\",\n      searchTerms: [\"ordered\"],\n      icon: ListOrderedIcon,\n      command: ({ editor, range }) => {\n        editor.chain().focus().deleteRange(range).toggleOrderedList().run();\n      },\n    },\n    {\n      title: \"Quote\",\n      description: \"Capture a quote.\",\n      searchTerms: [\"blockquote\"],\n      icon: TextQuoteIcon,\n      command: ({ editor, range }) =>\n        editor\n          .chain()\n          .focus()\n          .deleteRange(range)\n          .toggleNode(\"paragraph\", \"paragraph\")\n          .toggleBlockquote()\n          .run(),\n    },\n    {\n      title: \"Code\",\n      description: \"Capture a code snippet.\",\n      searchTerms: [\"codeblock\"],\n      icon: CodeIcon,\n      command: ({ editor, range }) =>\n        editor.chain().focus().deleteRange(range).toggleCodeBlock().run(),\n    },\n    {\n      title: \"Table\",\n      description: \"Add a table view to organize data.\",\n      searchTerms: [\"table\"],\n      icon: TableIcon,\n      command: ({ editor, range }) =>\n        editor\n          .chain()\n          .focus()\n          .deleteRange(range)\n          .insertTable({ rows: 3, cols: 3, withHeaderRow: true })\n          .run(),\n    },\n  ];\n\nconst Slash = Node.create<SlashOptions>({\n  name: \"slash\",\n  priority: 101,\n  addOptions() {\n    return {\n      HTMLAttributes: {},\n      renderText({ options, node }) {\n        return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`;\n      },\n      deleteTriggerWithBackspace: false,\n      renderHTML({ options, node }) {\n        return [\n          \"span\",\n          mergeAttributes(this.HTMLAttributes, options.HTMLAttributes),\n          `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`,\n        ];\n      },\n      suggestion: {\n        char: \"/\",\n        pluginKey: SlashPluginKey,\n        command: ({ editor, range, props }) => {\n          // increase range.to by one when the next node is of type \"text\"\n          // and starts with a space character\n          const nodeAfter = editor.view.state.selection.$to.nodeAfter;\n          const overrideSpace = nodeAfter?.text?.startsWith(\" \");\n\n          if (overrideSpace) {\n            range.to += 1;\n          }\n\n          editor\n            .chain()\n            .focus()\n            .insertContentAt(range, [\n              {\n                type: this.name,\n                attrs: props,\n              },\n              {\n                type: \"text\",\n                text: \" \",\n              },\n            ])\n            .run();\n\n          // get reference to `window` object from editor element, to support cross-frame JS usage\n          editor.view.dom.ownerDocument.defaultView\n            ?.getSelection()\n            ?.collapseToEnd();\n        },\n        allow: ({ state, range }) => {\n          const $from = state.doc.resolve(range.from);\n          const type = state.schema.nodes[this.name];\n          const allow = !!$from.parent.type.contentMatch.matchType(type);\n\n          return allow;\n        },\n      },\n    };\n  },\n\n  group: \"inline\",\n\n  inline: true,\n\n  selectable: false,\n\n  atom: true,\n\n  addAttributes() {\n    return {\n      id: {\n        default: null,\n        parseHTML: (element) => element.getAttribute(\"data-id\"),\n        renderHTML: (attributes) => {\n          if (!attributes.id) {\n            return {};\n          }\n\n          return {\n            \"data-id\": attributes.id,\n          };\n        },\n      },\n\n      label: {\n        default: null,\n        parseHTML: (element) => element.getAttribute(\"data-label\"),\n        renderHTML: (attributes) => {\n          if (!attributes.label) {\n            return {};\n          }\n\n          return {\n            \"data-label\": attributes.label,\n          };\n        },\n      },\n    };\n  },\n\n  parseHTML() {\n    return [\n      {\n        tag: `span[data-type=\"${this.name}\"]`,\n      },\n    ];\n  },\n\n  renderHTML({ node, HTMLAttributes }) {\n    const mergedOptions = { ...this.options };\n\n    mergedOptions.HTMLAttributes = mergeAttributes(\n      { \"data-type\": this.name },\n      this.options.HTMLAttributes,\n      HTMLAttributes\n    );\n    const html = this.options.renderHTML({\n      options: mergedOptions,\n      node,\n    });\n\n    if (typeof html === \"string\") {\n      return [\n        \"span\",\n        mergeAttributes(\n          { \"data-type\": this.name },\n          this.options.HTMLAttributes,\n          HTMLAttributes\n        ),\n        html,\n      ];\n    }\n    return html;\n  },\n\n  renderText({ node }) {\n    return this.options.renderText({\n      options: this.options,\n      node,\n    });\n  },\n\n  addKeyboardShortcuts() {\n    return {\n      Backspace: () =>\n        this.editor.commands.command(({ tr, state }) => {\n          let isMention = false;\n          const { selection } = state;\n          const { empty, anchor } = selection;\n\n          if (!empty) {\n            return false;\n          }\n\n          state.doc.nodesBetween(anchor - 1, anchor, (node, pos) => {\n            if (node.type.name === this.name) {\n              isMention = true;\n              tr.insertText(\n                this.options.deleteTriggerWithBackspace\n                  ? \"\"\n                  : this.options.suggestion.char || \"\",\n                pos,\n                pos + node.nodeSize\n              );\n\n              return false;\n            }\n          });\n\n          return isMention;\n        }),\n    };\n  },\n\n  addProseMirrorPlugins() {\n    return [\n      Suggestion({\n        editor: this.editor,\n        ...this.options.suggestion,\n      }),\n    ];\n  },\n});\n\n// Create a lowlight instance with all languages loaded\nconst lowlight = createLowlight(all);\n\ntype EditorSlashMenuProps = {\n  items: SuggestionItem[];\n  command: (item: SuggestionItem) => void;\n  editor: Editor;\n  range: Range;\n};\n\nconst EditorSlashMenu = ({ items, editor, range }: EditorSlashMenuProps) => (\n  <Command\n    className=\"border shadow\"\n    id=\"slash-command\"\n    onKeyDown={(e) => {\n      e.stopPropagation();\n    }}\n  >\n    <CommandEmpty className=\"flex w-full items-center justify-center p-4 text-muted-foreground text-sm\">\n      <p>No results</p>\n    </CommandEmpty>\n    <CommandList>\n      {items.map((item) => (\n        <CommandItem\n          className=\"flex items-center gap-3 pr-3\"\n          key={item.title}\n          onSelect={() => item.command({ editor, range })}\n        >\n          <div className=\"flex size-9 shrink-0 items-center justify-center rounded border bg-secondary\">\n            <item.icon className=\"text-muted-foreground\" size={16} />\n          </div>\n          <div className=\"flex flex-col\">\n            <span className=\"font-medium text-sm\">{item.title}</span>\n            <span className=\"text-muted-foreground text-xs\">\n              {item.description}\n            </span>\n          </div>\n        </CommandItem>\n      ))}\n    </CommandList>\n  </Command>\n);\n\nconst handleCommandNavigation = (event: KeyboardEvent) => {\n  if ([\"ArrowUp\", \"ArrowDown\", \"Enter\"].includes(event.key)) {\n    const slashCommand = document.querySelector(\"#slash-command\");\n\n    if (slashCommand) {\n      event.preventDefault();\n\n      slashCommand.dispatchEvent(\n        new KeyboardEvent(\"keydown\", {\n          key: event.key,\n          cancelable: true,\n          bubbles: true,\n        })\n      );\n\n      return true;\n    }\n  }\n};\n\nexport type EditorProviderProps = TiptapEditorProviderProps & {\n  className?: string;\n  limit?: number;\n  placeholder?: string;\n};\n\nexport const EditorProvider = ({\n  className,\n  extensions,\n  limit,\n  placeholder,\n  ...props\n}: EditorProviderProps) => {\n  const defaultExtensions = [\n    StarterKit.configure({\n      codeBlock: false,\n      bulletList: {\n        HTMLAttributes: {\n          class: cn(\"list-outside list-disc pl-4\"),\n        },\n      },\n      orderedList: {\n        HTMLAttributes: {\n          class: cn(\"list-outside list-decimal pl-4\"),\n        },\n      },\n      listItem: {\n        HTMLAttributes: {\n          class: cn(\"leading-normal\"),\n        },\n      },\n      blockquote: {\n        HTMLAttributes: {\n          class: cn(\"border-l border-l-2 pl-2\"),\n        },\n      },\n      code: {\n        HTMLAttributes: {\n          class: cn(\"rounded-md bg-muted px-1.5 py-1 font-medium font-mono\"),\n          spellcheck: \"false\",\n        },\n      },\n      horizontalRule: {\n        HTMLAttributes: {\n          class: cn(\"mt-4 mb-6 border-muted-foreground border-t\"),\n        },\n      },\n      dropcursor: {\n        color: \"var(--border)\",\n        width: 4,\n      },\n    }),\n    Typography,\n    Placeholder.configure({\n      placeholder,\n      emptyEditorClass:\n        \"before:text-muted-foreground before:content-[attr(data-placeholder)] before:float-left before:h-0 before:pointer-events-none\",\n    }),\n    CharacterCount.configure({\n      limit,\n    }),\n    CodeBlockLowlight.configure({\n      lowlight,\n      HTMLAttributes: {\n        class: cn(\n          \"rounded-md border p-4 text-sm\",\n          \"bg-background text-foreground\",\n          \"[&_.hljs-doctag]:text-[#d73a49] [&_.hljs-keyword]:text-[#d73a49] [&_.hljs-meta_.hljs-keyword]:text-[#d73a49] [&_.hljs-template-tag]:text-[#d73a49] [&_.hljs-template-variable]:text-[#d73a49] [&_.hljs-type]:text-[#d73a49] [&_.hljs-variable.language_]:text-[#d73a49]\",\n          \"[&_.hljs-title.class_.inherited__]:text-[#6f42c1] [&_.hljs-title.class_]:text-[#6f42c1] [&_.hljs-title.function_]:text-[#6f42c1] [&_.hljs-title]:text-[#6f42c1]\",\n          \"[&_.hljs-attr]:text-[#005cc5] [&_.hljs-attribute]:text-[#005cc5] [&_.hljs-literal]:text-[#005cc5] [&_.hljs-meta]:text-[#005cc5] [&_.hljs-number]:text-[#005cc5] [&_.hljs-operator]:text-[#005cc5] [&_.hljs-selector-attr]:text-[#005cc5] [&_.hljs-selector-class]:text-[#005cc5] [&_.hljs-selector-id]:text-[#005cc5] [&_.hljs-variable]:text-[#005cc5]\",\n          \"[&_.hljs-meta_.hljs-string]:text-[#032f62] [&_.hljs-regexp]:text-[#032f62] [&_.hljs-string]:text-[#032f62]\",\n          \"[&_.hljs-built_in]:text-[#e36209] [&_.hljs-symbol]:text-[#e36209]\",\n          \"[&_.hljs-code]:text-[#6a737d] [&_.hljs-comment]:text-[#6a737d] [&_.hljs-formula]:text-[#6a737d]\",\n          \"[&_.hljs-name]:text-[#22863a] [&_.hljs-quote]:text-[#22863a] [&_.hljs-selector-pseudo]:text-[#22863a] [&_.hljs-selector-tag]:text-[#22863a]\",\n          \"[&_.hljs-subst]:text-[#24292e]\",\n          \"[&_.hljs-section]:font-bold [&_.hljs-section]:text-[#005cc5]\",\n          \"[&_.hljs-bullet]:text-[#735c0f]\",\n          \"[&_.hljs-emphasis]:text-[#24292e] [&_.hljs-emphasis]:italic\",\n          \"[&_.hljs-strong]:font-bold [&_.hljs-strong]:text-[#24292e]\",\n          \"[&_.hljs-addition]:bg-[#f0fff4] [&_.hljs-addition]:text-[#22863a]\",\n          \"[&_.hljs-deletion]:bg-[#ffeef0] [&_.hljs-deletion]:text-[#b31d28]\"\n        ),\n      },\n    }),\n    Superscript,\n    Subscript,\n    Slash.configure({\n      suggestion: {\n        items: async ({ editor, query }) => {\n          const items = await defaultSlashSuggestions({ editor, query });\n\n          if (!query) {\n            return items;\n          }\n\n          const slashFuse = new Fuse(items, {\n            keys: [\"title\", \"description\", \"searchTerms\"],\n            threshold: 0.2,\n            minMatchCharLength: 1,\n          });\n\n          const results = slashFuse.search(query);\n\n          return results.map((result) => result.item);\n        },\n        char: \"/\",\n        render: () => {\n          let component: ReactRenderer<EditorSlashMenuProps>;\n          let popup: TippyInstance;\n\n          return {\n            onStart: (onStartProps) => {\n              component = new ReactRenderer(EditorSlashMenu, {\n                props: onStartProps,\n                editor: onStartProps.editor,\n              });\n\n              popup = tippy(document.body, {\n                getReferenceClientRect: () =>\n                  onStartProps.clientRect?.() || new DOMRect(),\n                appendTo: () => document.body,\n                content: component.element,\n                showOnCreate: true,\n                interactive: true,\n                trigger: \"manual\",\n                placement: \"bottom-start\",\n              });\n            },\n\n            onUpdate(onUpdateProps) {\n              component.updateProps(onUpdateProps);\n\n              popup.setProps({\n                getReferenceClientRect: () =>\n                  onUpdateProps.clientRect?.() || new DOMRect(),\n              });\n            },\n\n            onKeyDown(onKeyDownProps) {\n              if (onKeyDownProps.event.key === \"Escape\") {\n                popup.hide();\n                component.destroy();\n\n                return true;\n              }\n\n              return handleCommandNavigation(onKeyDownProps.event) ?? false;\n            },\n\n            onExit() {\n              popup.destroy();\n              component.destroy();\n            },\n          };\n        },\n      },\n    }),\n    Table.configure({\n      HTMLAttributes: {\n        class: cn(\n          \"relative m-0 mx-auto my-3 w-full table-fixed border-collapse overflow-hidden rounded-none text-sm\"\n        ),\n      },\n      allowTableNodeSelection: true,\n    }),\n    TableRow.configure({\n      HTMLAttributes: {\n        class: cn(\n          \"relative box-border min-w-[1em] border p-1 text-start align-top\"\n        ),\n      },\n    }),\n    TableCell.configure({\n      HTMLAttributes: {\n        class: cn(\n          \"relative box-border min-w-[1em] border p-1 text-start align-top\"\n        ),\n      },\n    }),\n    TableHeader.configure({\n      HTMLAttributes: {\n        class: cn(\n          \"relative box-border min-w-[1em] border bg-secondary p-1 text-start align-top font-medium font-semibold text-muted-foreground\"\n        ),\n      },\n    }),\n    TaskList.configure({\n      HTMLAttributes: {\n        // 17px = the width of the checkbox + the gap between the checkbox and the text\n        class: \"before:translate-x-[17px]\",\n      },\n    }),\n    TaskItem.configure({\n      HTMLAttributes: {\n        class: \"flex items-start gap-1\",\n      },\n    }),\n  ];\n\n  return (\n    <TooltipProvider>\n      <div className={cn(className, \"[&_.ProseMirror-focused]:outline-none\")}>\n        <TiptapEditorProvider\n          editorProps={{\n            handleKeyDown: (_view, event) => {\n              handleCommandNavigation(event);\n            },\n          }}\n          extensions={[\n            ...defaultExtensions,\n            TextStyleKit,\n            ...(extensions ?? []),\n          ]}\n          immediatelyRender={false}\n          {...props}\n        />\n      </div>\n    </TooltipProvider>\n  );\n};\n\nexport type EditorFloatingMenuProps = Omit<FloatingMenuProps, \"editor\">;\n\nexport const EditorFloatingMenu = ({\n  className,\n  ...props\n}: EditorFloatingMenuProps) => {\n  const { editor } = useCurrentEditor();\n  return (\n    <FloatingMenu\n      className={cn(\"flex items-center bg-secondary\", className)}\n      editor={editor ?? null}\n      {...props}\n    />\n  );\n};\n\nexport type EditorBubbleMenuProps = Omit<BubbleMenuProps, \"editor\">;\n\nexport const EditorBubbleMenu = ({\n  className,\n  children,\n  ...props\n}: EditorBubbleMenuProps) => {\n  const { editor } = useCurrentEditor();\n  return (\n    <BubbleMenu\n      className={cn(\n        \"flex rounded-xl border bg-background p-0.5 shadow\",\n        \"[&>*:first-child]:rounded-l-[9px]\",\n        \"[&>*:last-child]:rounded-r-[9px]\",\n        className\n      )}\n      editor={editor ?? undefined}\n      {...props}\n    >\n      {children && Array.isArray(children)\n        ? children.reduce((acc: ReactNode[], child, index) => {\n            if (index === 0) {\n              return [child];\n            }\n\n            // biome-ignore lint/suspicious/noArrayIndexKey: \"only iterator we have\"\n            acc.push(<Separator key={index} orientation=\"vertical\" />);\n            acc.push(child);\n            return acc;\n          }, [])\n        : children}\n    </BubbleMenu>\n  );\n};\n\ntype EditorButtonProps = {\n  name: string;\n  isActive: () => boolean;\n  command: () => void;\n  icon: LucideIcon | ((props: LucideProps) => ReactNode);\n  hideName?: boolean;\n};\n\nconst BubbleMenuButton = ({\n  name,\n  isActive,\n  command,\n  icon: Icon,\n  hideName,\n}: EditorButtonProps) => (\n  <Button\n    className={`flex gap-4 ${hideName ? \"\" : \"w-full\"}`}\n    onClick={() => command()}\n    size=\"sm\"\n    variant=\"ghost\"\n  >\n    <Icon className=\"shrink-0 text-muted-foreground\" size={12} />\n    {!hideName && <span className=\"flex-1 text-left\">{name}</span>}\n    {isActive() ? (\n      <CheckIcon className=\"shrink-0 text-muted-foreground\" size={12} />\n    ) : null}\n  </Button>\n);\n\nexport type EditorClearFormattingProps = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorClearFormatting = ({\n  hideName = true,\n}: EditorClearFormattingProps) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() => editor.chain().focus().clearNodes().unsetAllMarks().run()}\n      hideName={hideName}\n      icon={RemoveFormattingIcon}\n      isActive={() => false}\n      name=\"Clear Formatting\"\n    />\n  );\n};\n\nexport type EditorNodeTextProps = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorNodeText = ({\n  hideName = false,\n}: Pick<EditorButtonProps, \"hideName\">) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() =>\n        editor.chain().focus().toggleNode(\"paragraph\", \"paragraph\").run()\n      }\n      hideName={hideName}\n      // I feel like there has to be a more efficient way to do this – feel free to PR if you know how!\n      icon={TextIcon}\n      isActive={() =>\n        (editor &&\n          !editor.isActive(\"paragraph\") &&\n          !editor.isActive(\"bulletList\") &&\n          !editor.isActive(\"orderedList\")) ??\n        false\n      }\n      name=\"Text\"\n    />\n  );\n};\n\nexport type EditorNodeHeading1Props = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorNodeHeading1 = ({\n  hideName = false,\n}: Pick<EditorButtonProps, \"hideName\">) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}\n      hideName={hideName}\n      icon={Heading1Icon}\n      isActive={() => editor.isActive(\"heading\", { level: 1 }) ?? false}\n      name=\"Heading 1\"\n    />\n  );\n};\n\nexport type EditorNodeHeading2Props = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorNodeHeading2 = ({\n  hideName = false,\n}: Pick<EditorButtonProps, \"hideName\">) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}\n      hideName={hideName}\n      icon={Heading2Icon}\n      isActive={() => editor.isActive(\"heading\", { level: 2 }) ?? false}\n      name=\"Heading 2\"\n    />\n  );\n};\n\nexport type EditorNodeHeading3Props = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorNodeHeading3 = ({\n  hideName = false,\n}: Pick<EditorButtonProps, \"hideName\">) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}\n      hideName={hideName}\n      icon={Heading3Icon}\n      isActive={() => editor.isActive(\"heading\", { level: 3 }) ?? false}\n      name=\"Heading 3\"\n    />\n  );\n};\n\nexport type EditorNodeBulletListProps = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorNodeBulletList = ({\n  hideName = false,\n}: Pick<EditorButtonProps, \"hideName\">) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() => editor.chain().focus().toggleBulletList().run()}\n      hideName={hideName}\n      icon={ListIcon}\n      isActive={() => editor.isActive(\"bulletList\") ?? false}\n      name=\"Bullet List\"\n    />\n  );\n};\n\nexport type EditorNodeOrderedListProps = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorNodeOrderedList = ({\n  hideName = false,\n}: Pick<EditorButtonProps, \"hideName\">) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() => editor.chain().focus().toggleOrderedList().run()}\n      hideName={hideName}\n      icon={ListOrderedIcon}\n      isActive={() => editor.isActive(\"orderedList\") ?? false}\n      name=\"Numbered List\"\n    />\n  );\n};\n\nexport type EditorNodeTaskListProps = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorNodeTaskList = ({\n  hideName = false,\n}: Pick<EditorButtonProps, \"hideName\">) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() =>\n        editor.chain().focus().toggleList(\"taskList\", \"taskItem\").run()\n      }\n      hideName={hideName}\n      icon={CheckSquareIcon}\n      isActive={() => editor.isActive(\"taskItem\") ?? false}\n      name=\"To-do List\"\n    />\n  );\n};\n\nexport type EditorNodeQuoteProps = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorNodeQuote = ({\n  hideName = false,\n}: Pick<EditorButtonProps, \"hideName\">) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() =>\n        editor\n          .chain()\n          .focus()\n          .toggleNode(\"paragraph\", \"paragraph\")\n          .toggleBlockquote()\n          .run()\n      }\n      hideName={hideName}\n      icon={TextQuoteIcon}\n      isActive={() => editor.isActive(\"blockquote\") ?? false}\n      name=\"Quote\"\n    />\n  );\n};\n\nexport type EditorNodeCodeProps = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorNodeCode = ({\n  hideName = false,\n}: Pick<EditorButtonProps, \"hideName\">) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() => editor.chain().focus().toggleCodeBlock().run()}\n      hideName={hideName}\n      icon={CodeIcon}\n      isActive={() => editor.isActive(\"codeBlock\") ?? false}\n      name=\"Code\"\n    />\n  );\n};\n\nexport type EditorNodeTableProps = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorNodeTable = ({\n  hideName = false,\n}: Pick<EditorButtonProps, \"hideName\">) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() =>\n        editor\n          .chain()\n          .focus()\n          .insertTable({ rows: 3, cols: 3, withHeaderRow: true })\n          .run()\n      }\n      hideName={hideName}\n      icon={TableIcon}\n      isActive={() => editor.isActive(\"table\") ?? false}\n      name=\"Table\"\n    />\n  );\n};\n\nexport type EditorSelectorProps = HTMLAttributes<HTMLDivElement> & {\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  title: string;\n};\n\nexport const EditorSelector = ({\n  open,\n  onOpenChange,\n  title,\n  className,\n  children,\n  ...props\n}: EditorSelectorProps) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <Popover onOpenChange={onOpenChange} open={open}>\n      <PopoverTrigger asChild>\n        <Button\n          className=\"gap-2 rounded-none border-none\"\n          size=\"sm\"\n          variant=\"ghost\"\n        >\n          <span className=\"whitespace-nowrap text-xs\">{title}</span>\n          <ChevronDownIcon size={12} />\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent\n        align=\"start\"\n        className={cn(\"w-48 p-1\", className)}\n        sideOffset={5}\n        {...props}\n      >\n        {children}\n      </PopoverContent>\n    </Popover>\n  );\n};\n\nexport type EditorFormatBoldProps = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorFormatBold = ({\n  hideName = false,\n}: Pick<EditorButtonProps, \"hideName\">) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() => editor.chain().focus().toggleBold().run()}\n      hideName={hideName}\n      icon={BoldIcon}\n      isActive={() => editor.isActive(\"bold\") ?? false}\n      name=\"Bold\"\n    />\n  );\n};\n\nexport type EditorFormatItalicProps = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorFormatItalic = ({\n  hideName = false,\n}: Pick<EditorButtonProps, \"hideName\">) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() => editor.chain().focus().toggleItalic().run()}\n      hideName={hideName}\n      icon={ItalicIcon}\n      isActive={() => editor.isActive(\"italic\") ?? false}\n      name=\"Italic\"\n    />\n  );\n};\n\nexport type EditorFormatStrikeProps = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorFormatStrike = ({\n  hideName = false,\n}: Pick<EditorButtonProps, \"hideName\">) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() => editor.chain().focus().toggleStrike().run()}\n      hideName={hideName}\n      icon={StrikethroughIcon}\n      isActive={() => editor.isActive(\"strike\") ?? false}\n      name=\"Strikethrough\"\n    />\n  );\n};\n\nexport type EditorFormatCodeProps = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorFormatCode = ({\n  hideName = false,\n}: Pick<EditorButtonProps, \"hideName\">) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() => editor.chain().focus().toggleCode().run()}\n      hideName={hideName}\n      icon={CodeIcon}\n      isActive={() => editor.isActive(\"code\") ?? false}\n      name=\"Code\"\n    />\n  );\n};\n\nexport type EditorFormatSubscriptProps = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorFormatSubscript = ({\n  hideName = false,\n}: Pick<EditorButtonProps, \"hideName\">) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() => editor.chain().focus().toggleSubscript().run()}\n      hideName={hideName}\n      icon={SubscriptIcon}\n      isActive={() => editor.isActive(\"subscript\") ?? false}\n      name=\"Subscript\"\n    />\n  );\n};\n\nexport type EditorFormatSuperscriptProps = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorFormatSuperscript = ({\n  hideName = false,\n}: Pick<EditorButtonProps, \"hideName\">) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() => editor.chain().focus().toggleSuperscript().run()}\n      hideName={hideName}\n      icon={SuperscriptIcon}\n      isActive={() => editor.isActive(\"superscript\") ?? false}\n      name=\"Superscript\"\n    />\n  );\n};\n\nexport type EditorFormatUnderlineProps = Pick<EditorButtonProps, \"hideName\">;\n\nexport const EditorFormatUnderline = ({\n  hideName = false,\n}: Pick<EditorButtonProps, \"hideName\">) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <BubbleMenuButton\n      command={() => editor.chain().focus().toggleUnderline().run()}\n      hideName={hideName}\n      icon={UnderlineIcon}\n      isActive={() => editor.isActive(\"underline\") ?? false}\n      name=\"Underline\"\n    />\n  );\n};\n\nexport type EditorLinkSelectorProps = {\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n};\n\nexport const EditorLinkSelector = ({\n  open,\n  onOpenChange,\n}: EditorLinkSelectorProps) => {\n  const [url, setUrl] = useState<string>(\"\");\n  const inputReference = useRef<HTMLInputElement>(null);\n  const { editor } = useCurrentEditor();\n\n  const isValidUrl = (text: string): boolean => {\n    try {\n      new URL(text);\n      return true;\n    } catch {\n      return false;\n    }\n  };\n\n  const getUrlFromString = (text: string): string | null => {\n    if (isValidUrl(text)) {\n      return text;\n    }\n    try {\n      if (text.includes(\".\") && !text.includes(\" \")) {\n        return new URL(`https://${text}`).toString();\n      }\n\n      return null;\n    } catch {\n      return null;\n    }\n  };\n\n  useEffect(() => {\n    inputReference.current?.focus();\n  }, []);\n\n  if (!editor) {\n    return null;\n  }\n\n  const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {\n    event.preventDefault();\n\n    const href = getUrlFromString(url);\n\n    if (href) {\n      editor.chain().focus().setLink({ href }).run();\n      onOpenChange?.(false);\n    }\n  };\n\n  const defaultValue = (editor.getAttributes(\"link\") as { href?: string }).href;\n\n  return (\n    <Popover modal onOpenChange={onOpenChange} open={open}>\n      <PopoverTrigger asChild>\n        <Button\n          className=\"gap-2 rounded-none border-none\"\n          size=\"sm\"\n          variant=\"ghost\"\n        >\n          <ExternalLinkIcon size={12} />\n          <p\n            className={cn(\n              \"text-xs underline decoration-text-muted underline-offset-4\",\n              {\n                \"text-primary\": editor.isActive(\"link\"),\n              }\n            )}\n          >\n            Link\n          </p>\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent align=\"start\" className=\"w-60 p-0\" sideOffset={10}>\n        <form className=\"flex p-1\" onSubmit={handleSubmit}>\n          <input\n            aria-label=\"Link URL\"\n            className=\"flex-1 bg-background p-1 text-sm outline-none\"\n            defaultValue={defaultValue ?? \"\"}\n            onChange={(event) => setUrl(event.target.value)}\n            placeholder=\"Paste a link\"\n            ref={inputReference}\n            type=\"text\"\n            value={url}\n          />\n          {editor.getAttributes(\"link\").href ? (\n            <Button\n              className=\"flex h-8 items-center rounded-sm p-1 text-destructive transition-all hover:bg-destructive-foreground dark:hover:bg-destructive\"\n              onClick={() => {\n                editor.chain().focus().unsetLink().run();\n                onOpenChange?.(false);\n              }}\n              size=\"icon\"\n              type=\"button\"\n              variant=\"outline\"\n            >\n              <TrashIcon size={12} />\n            </Button>\n          ) : (\n            <Button className=\"h-8\" size=\"icon\" variant=\"secondary\">\n              <CheckIcon size={12} />\n            </Button>\n          )}\n        </form>\n      </PopoverContent>\n    </Popover>\n  );\n};\n\nexport type EditorTableMenuProps = {\n  children: ReactNode;\n};\n\nexport const EditorTableMenu = ({ children }: EditorTableMenuProps) => {\n  const { editor } = useCurrentEditor();\n\n  if (!editor) {\n    return null;\n  }\n\n  const isActive = editor.isActive(\"table\");\n\n  return (\n    <div\n      className={cn({\n        hidden: !isActive,\n      })}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport type EditorTableGlobalMenuProps = {\n  children: ReactNode;\n};\n\nexport const EditorTableGlobalMenu = ({\n  children,\n}: EditorTableGlobalMenuProps) => {\n  const { editor } = useCurrentEditor();\n  const [top, setTop] = useState(0);\n  const [left, setLeft] = useState(0);\n\n  useEffect(() => {\n    if (!editor) {\n      return;\n    }\n\n    editor.on(\"selectionUpdate\", () => {\n      const selection = window.getSelection();\n\n      if (!selection) {\n        return;\n      }\n\n      const range = selection.getRangeAt(0);\n      let startContainer = range.startContainer as HTMLElement | string;\n\n      if (!(startContainer instanceof HTMLElement)) {\n        startContainer = range.startContainer.parentElement as HTMLElement;\n      }\n\n      const tableNode = startContainer.closest(\"table\");\n\n      if (!tableNode) {\n        return;\n      }\n\n      const tableRect = tableNode.getBoundingClientRect();\n\n      setTop(tableRect.top + tableRect.height);\n      setLeft(tableRect.left + tableRect.width / 2);\n    });\n\n    return () => {\n      editor.off(\"selectionUpdate\");\n    };\n  }, [editor]);\n\n  return (\n    <div\n      className={cn(\n        \"-translate-x-1/2 absolute flex translate-y-1/2 items-center rounded-full border bg-background shadow-xl\",\n        {\n          hidden: !(left || top),\n        }\n      )}\n      style={{ top, left }}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport type EditorTableColumnMenuProps = {\n  children: ReactNode;\n};\n\nexport const EditorTableColumnMenu = ({\n  children,\n}: EditorTableColumnMenuProps) => {\n  const { editor } = useCurrentEditor();\n  const [top, setTop] = useState(0);\n  const [left, setLeft] = useState(0);\n\n  useEffect(() => {\n    if (!editor) {\n      return;\n    }\n\n    editor.on(\"selectionUpdate\", () => {\n      const selection = window.getSelection();\n\n      if (!selection) {\n        return;\n      }\n\n      const range = selection.getRangeAt(0);\n      let startContainer = range.startContainer as HTMLElement | string;\n\n      if (!(startContainer instanceof HTMLElement)) {\n        startContainer = range.startContainer.parentElement as HTMLElement;\n      }\n\n      // Get the closest table cell (td or th)\n      const tableCell = startContainer.closest(\"td, th\");\n\n      if (!tableCell) {\n        return;\n      }\n\n      const cellRect = tableCell.getBoundingClientRect();\n\n      setTop(cellRect.top);\n      setLeft(cellRect.left + cellRect.width / 2);\n    });\n\n    return () => {\n      editor.off(\"selectionUpdate\");\n    };\n  }, [editor]);\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        asChild\n        className={cn(\n          \"-translate-x-1/2 -translate-y-1/2 absolute flex h-4 w-7 overflow-hidden rounded-md border bg-background shadow-xl\",\n          {\n            hidden: !(left || top),\n          }\n        )}\n        style={{ top, left }}\n      >\n        <Button size=\"icon\" variant=\"ghost\">\n          <EllipsisIcon className=\"text-muted-foreground\" size={16} />\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent>{children}</DropdownMenuContent>\n    </DropdownMenu>\n  );\n};\n\nexport type EditorTableRowMenuProps = {\n  children: ReactNode;\n};\n\nexport const EditorTableRowMenu = ({ children }: EditorTableRowMenuProps) => {\n  const { editor } = useCurrentEditor();\n  const [top, setTop] = useState(0);\n  const [left, setLeft] = useState(0);\n\n  useEffect(() => {\n    if (!editor) {\n      return;\n    }\n\n    editor.on(\"selectionUpdate\", () => {\n      const selection = window.getSelection();\n\n      if (!selection) {\n        return;\n      }\n\n      const range = selection.getRangeAt(0);\n      let startContainer = range.startContainer as HTMLElement | string;\n\n      if (!(startContainer instanceof HTMLElement)) {\n        startContainer = range.startContainer.parentElement as HTMLElement;\n      }\n\n      const tableRow = startContainer.closest(\"tr\");\n\n      if (!tableRow) {\n        return;\n      }\n\n      const rowRect = tableRow.getBoundingClientRect();\n\n      setTop(rowRect.top + rowRect.height / 2);\n      setLeft(rowRect.left);\n    });\n\n    return () => {\n      editor.off(\"selectionUpdate\");\n    };\n  }, [editor]);\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button\n          className={cn(\n            \"-translate-x-1/2 -translate-y-1/2 absolute flex h-7 w-4 overflow-hidden rounded-md border bg-background shadow-xl\",\n            {\n              hidden: !(left || top),\n            }\n          )}\n          size=\"icon\"\n          style={{ top, left }}\n          variant=\"ghost\"\n        >\n          <EllipsisVerticalIcon className=\"text-muted-foreground\" size={12} />\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent>{children}</DropdownMenuContent>\n    </DropdownMenu>\n  );\n};\n\nexport const EditorTableColumnBefore = () => {\n  const { editor } = useCurrentEditor();\n\n  const handleClick = useCallback(() => {\n    if (editor) {\n      editor.chain().focus().addColumnBefore().run();\n    }\n  }, [editor]);\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <DropdownMenuItem className=\"flex items-center gap-2\" onClick={handleClick}>\n      <ArrowLeftIcon className=\"text-muted-foreground\" size={16} />\n      <span>Add column before</span>\n    </DropdownMenuItem>\n  );\n};\n\nexport const EditorTableColumnAfter = () => {\n  const { editor } = useCurrentEditor();\n\n  const handleClick = useCallback(() => {\n    if (editor) {\n      editor.chain().focus().addColumnAfter().run();\n    }\n  }, [editor]);\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <DropdownMenuItem className=\"flex items-center gap-2\" onClick={handleClick}>\n      <ArrowRightIcon className=\"text-muted-foreground\" size={16} />\n      <span>Add column after</span>\n    </DropdownMenuItem>\n  );\n};\n\nexport const EditorTableRowBefore = () => {\n  const { editor } = useCurrentEditor();\n\n  const handleClick = useCallback(() => {\n    if (editor) {\n      editor.chain().focus().addRowBefore().run();\n    }\n  }, [editor]);\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <DropdownMenuItem className=\"flex items-center gap-2\" onClick={handleClick}>\n      <ArrowUpIcon className=\"text-muted-foreground\" size={16} />\n      <span>Add row before</span>\n    </DropdownMenuItem>\n  );\n};\n\nexport const EditorTableRowAfter = () => {\n  const { editor } = useCurrentEditor();\n\n  const handleClick = useCallback(() => {\n    if (editor) {\n      editor.chain().focus().addRowAfter().run();\n    }\n  }, [editor]);\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <DropdownMenuItem className=\"flex items-center gap-2\" onClick={handleClick}>\n      <ArrowDownIcon className=\"text-muted-foreground\" size={16} />\n      <span>Add row after</span>\n    </DropdownMenuItem>\n  );\n};\n\nexport const EditorTableColumnDelete = () => {\n  const { editor } = useCurrentEditor();\n\n  const handleClick = useCallback(() => {\n    if (editor) {\n      editor.chain().focus().deleteColumn().run();\n    }\n  }, [editor]);\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <DropdownMenuItem className=\"flex items-center gap-2\" onClick={handleClick}>\n      <TrashIcon className=\"text-destructive\" size={16} />\n      <span>Delete column</span>\n    </DropdownMenuItem>\n  );\n};\n\nexport const EditorTableRowDelete = () => {\n  const { editor } = useCurrentEditor();\n\n  const handleClick = useCallback(() => {\n    if (editor) {\n      editor.chain().focus().deleteRow().run();\n    }\n  }, [editor]);\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <DropdownMenuItem className=\"flex items-center gap-2\" onClick={handleClick}>\n      <TrashIcon className=\"text-destructive\" size={16} />\n      <span>Delete row</span>\n    </DropdownMenuItem>\n  );\n};\n\nexport const EditorTableHeaderColumnToggle = () => {\n  const { editor } = useCurrentEditor();\n\n  const handleClick = useCallback(() => {\n    if (editor) {\n      editor.chain().focus().toggleHeaderColumn().run();\n    }\n  }, [editor]);\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>\n        <Button\n          className=\"flex items-center gap-2 rounded-full\"\n          onClick={handleClick}\n          size=\"icon\"\n          variant=\"ghost\"\n        >\n          <ColumnsIcon className=\"text-muted-foreground\" size={16} />\n        </Button>\n      </TooltipTrigger>\n      <TooltipContent>\n        <span>Toggle header column</span>\n      </TooltipContent>\n    </Tooltip>\n  );\n};\n\nexport const EditorTableHeaderRowToggle = () => {\n  const { editor } = useCurrentEditor();\n\n  const handleClick = useCallback(() => {\n    if (editor) {\n      editor.chain().focus().toggleHeaderRow().run();\n    }\n  }, [editor]);\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>\n        <Button\n          className=\"flex items-center gap-2 rounded-full\"\n          onClick={handleClick}\n          size=\"icon\"\n          variant=\"ghost\"\n        >\n          <RowsIcon className=\"text-muted-foreground\" size={16} />\n        </Button>\n      </TooltipTrigger>\n      <TooltipContent>\n        <span>Toggle header row</span>\n      </TooltipContent>\n    </Tooltip>\n  );\n};\n\nexport const EditorTableDelete = () => {\n  const { editor } = useCurrentEditor();\n\n  const handleClick = useCallback(() => {\n    if (editor) {\n      editor.chain().focus().deleteTable().run();\n    }\n  }, [editor]);\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>\n        <Button\n          className=\"flex items-center gap-2 rounded-full\"\n          onClick={handleClick}\n          size=\"icon\"\n          variant=\"ghost\"\n        >\n          <TrashIcon className=\"text-destructive\" size={16} />\n        </Button>\n      </TooltipTrigger>\n      <TooltipContent>\n        <span>Delete table</span>\n      </TooltipContent>\n    </Tooltip>\n  );\n};\n\nexport const EditorTableMergeCells = () => {\n  const { editor } = useCurrentEditor();\n\n  const handleClick = useCallback(() => {\n    if (editor) {\n      editor.chain().focus().mergeCells().run();\n    }\n  }, [editor]);\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>\n        <Button\n          className=\"flex items-center gap-2 rounded-full\"\n          onClick={handleClick}\n          size=\"icon\"\n          variant=\"ghost\"\n        >\n          <TableCellsMergeIcon className=\"text-muted-foreground\" size={16} />\n        </Button>\n      </TooltipTrigger>\n      <TooltipContent>\n        <span>Merge cells</span>\n      </TooltipContent>\n    </Tooltip>\n  );\n};\n\nexport const EditorTableSplitCell = () => {\n  const { editor } = useCurrentEditor();\n\n  const handleClick = useCallback(() => {\n    if (editor) {\n      editor.chain().focus().splitCell().run();\n    }\n  }, [editor]);\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>\n        <Button\n          className=\"flex items-center gap-2 rounded-full\"\n          onClick={handleClick}\n          size=\"icon\"\n          variant=\"ghost\"\n        >\n          <TableColumnsSplitIcon className=\"text-muted-foreground\" size={16} />\n        </Button>\n      </TooltipTrigger>\n      <TooltipContent>\n        <span>Split cell</span>\n      </TooltipContent>\n    </Tooltip>\n  );\n};\n\nexport const EditorTableFix = () => {\n  const { editor } = useCurrentEditor();\n\n  const handleClick = useCallback(() => {\n    if (editor) {\n      editor.chain().focus().fixTables().run();\n    }\n  }, [editor]);\n\n  if (!editor) {\n    return null;\n  }\n\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>\n        <Button\n          className=\"flex items-center gap-2 rounded-full\"\n          onClick={handleClick}\n          size=\"icon\"\n          variant=\"ghost\"\n        >\n          <BoltIcon className=\"text-muted-foreground\" size={16} />\n        </Button>\n      </TooltipTrigger>\n      <TooltipContent>\n        <span>Fix table</span>\n      </TooltipContent>\n    </Tooltip>\n  );\n};\n\nexport type EditorCharacterCountProps = {\n  children: ReactNode;\n  className?: string;\n};\n\nexport const EditorCharacterCount = {\n  Characters({ children, className }: EditorCharacterCountProps) {\n    const { editor } = useCurrentEditor();\n\n    if (!editor) {\n      return null;\n    }\n\n    return (\n      <div\n        className={cn(\n          \"absolute right-4 bottom-4 rounded-md border bg-background p-2 text-muted-foreground text-sm shadow\",\n          className\n        )}\n      >\n        {children}\n        {editor.storage.characterCount.characters()}\n      </div>\n    );\n  },\n\n  Words({ children, className }: EditorCharacterCountProps) {\n    const { editor } = useCurrentEditor();\n\n    if (!editor) {\n      return null;\n    }\n\n    return (\n      <div\n        className={cn(\n          \"absolute right-4 bottom-4 rounded-md border bg-background p-2 text-muted-foreground text-sm shadow\",\n          className\n        )}\n      >\n        {children}\n        {editor.storage.characterCount.words()}\n      </div>\n    );\n  },\n};\n","target":"components/kibo-ui/editor/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"gantt","type":"registry:ui","title":"gantt","description":"The Gantt chart is a powerful tool for visualizing project schedules and tracking the progress of tasks. It provides a clear, hierarchical view of tasks, allowing you to easily identify manage project timelines.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@dnd-kit/core","@dnd-kit/modifiers","@uidotdev/usehooks","date-fns","jotai","lodash.throttle","lucide-react"],"devDependencies":["@types/lodash.throttle"],"registryDependencies":["card","context-menu"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport {\n  DndContext,\n  MouseSensor,\n  useDraggable,\n  useSensor,\n} from \"@dnd-kit/core\";\nimport { restrictToHorizontalAxis } from \"@dnd-kit/modifiers\";\nimport { useMouse, useThrottle, useWindowScroll } from \"@uidotdev/usehooks\";\nimport {\n  addDays,\n  addMonths,\n  differenceInDays,\n  differenceInHours,\n  differenceInMonths,\n  endOfDay,\n  endOfMonth,\n  format,\n  formatDate,\n  formatDistance,\n  getDate,\n  getDaysInMonth,\n  isSameDay,\n  startOfDay,\n  startOfMonth,\n} from \"date-fns\";\nimport { atom, useAtom } from \"jotai\";\nimport throttle from \"lodash.throttle\";\nimport { PlusIcon, TrashIcon } from \"lucide-react\";\nimport type {\n  CSSProperties,\n  FC,\n  KeyboardEventHandler,\n  MouseEventHandler,\n  ReactNode,\n  RefObject,\n} from \"react\";\nimport {\n  createContext,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport { Card } from \"@/components/ui/card\";\nimport {\n  ContextMenu,\n  ContextMenuContent,\n  ContextMenuItem,\n  ContextMenuTrigger,\n} from \"@/components/ui/context-menu\";\nimport { cn } from \"@/lib/utils\";\n\nconst draggingAtom = atom(false);\nconst scrollXAtom = atom(0);\n\nexport const useGanttDragging = () => useAtom(draggingAtom);\nexport const useGanttScrollX = () => useAtom(scrollXAtom);\n\nexport type GanttStatus = {\n  id: string;\n  name: string;\n  color: string;\n};\n\nexport type GanttFeature = {\n  id: string;\n  name: string;\n  startAt: Date;\n  endAt: Date;\n  status: GanttStatus;\n  lane?: string; // Optional: features with the same lane will share a row\n};\n\nexport type GanttMarkerProps = {\n  id: string;\n  date: Date;\n  label: string;\n};\n\nexport type Range = \"daily\" | \"monthly\" | \"quarterly\";\n\nexport type TimelineData = {\n  year: number;\n  quarters: {\n    months: {\n      days: number;\n    }[];\n  }[];\n}[];\n\nexport type GanttContextProps = {\n  zoom: number;\n  range: Range;\n  columnWidth: number;\n  sidebarWidth: number;\n  headerHeight: number;\n  rowHeight: number;\n  onAddItem: ((date: Date) => void) | undefined;\n  placeholderLength: number;\n  timelineData: TimelineData;\n  ref: RefObject<HTMLDivElement | null> | null;\n  scrollToFeature?: (feature: GanttFeature) => void;\n};\n\nconst getsDaysIn = (range: Range) => {\n  // For when range is daily\n  let fn = (_date: Date) => 1;\n\n  if (range === \"monthly\" || range === \"quarterly\") {\n    fn = getDaysInMonth;\n  }\n\n  return fn;\n};\n\nconst getDifferenceIn = (range: Range) => {\n  let fn = differenceInDays;\n\n  if (range === \"monthly\" || range === \"quarterly\") {\n    fn = differenceInMonths;\n  }\n\n  return fn;\n};\n\nconst getInnerDifferenceIn = (range: Range) => {\n  let fn = differenceInHours;\n\n  if (range === \"monthly\" || range === \"quarterly\") {\n    fn = differenceInDays;\n  }\n\n  return fn;\n};\n\nconst getStartOf = (range: Range) => {\n  let fn = startOfDay;\n\n  if (range === \"monthly\" || range === \"quarterly\") {\n    fn = startOfMonth;\n  }\n\n  return fn;\n};\n\nconst getEndOf = (range: Range) => {\n  let fn = endOfDay;\n\n  if (range === \"monthly\" || range === \"quarterly\") {\n    fn = endOfMonth;\n  }\n\n  return fn;\n};\n\nconst getAddRange = (range: Range) => {\n  let fn = addDays;\n\n  if (range === \"monthly\" || range === \"quarterly\") {\n    fn = addMonths;\n  }\n\n  return fn;\n};\n\nconst getDateByMousePosition = (context: GanttContextProps, mouseX: number) => {\n  const timelineStartDate = new Date(context.timelineData[0].year, 0, 1);\n  const columnWidth = (context.columnWidth * context.zoom) / 100;\n  const offset = Math.floor(mouseX / columnWidth);\n  const daysIn = getsDaysIn(context.range);\n  const addRange = getAddRange(context.range);\n  const month = addRange(timelineStartDate, offset);\n  const daysInMonth = daysIn(month);\n  const pixelsPerDay = Math.round(columnWidth / daysInMonth);\n  const dayOffset = Math.floor((mouseX % columnWidth) / pixelsPerDay);\n  const actualDate = addDays(month, dayOffset);\n\n  return actualDate;\n};\n\nconst createInitialTimelineData = (today: Date) => {\n  const data: TimelineData = [];\n\n  data.push(\n    { year: today.getFullYear() - 1, quarters: new Array(4).fill(null) },\n    { year: today.getFullYear(), quarters: new Array(4).fill(null) },\n    { year: today.getFullYear() + 1, quarters: new Array(4).fill(null) }\n  );\n\n  for (const yearObj of data) {\n    yearObj.quarters = new Array(4).fill(null).map((_, quarterIndex) => ({\n      months: new Array(3).fill(null).map((_, monthIndex) => {\n        const month = quarterIndex * 3 + monthIndex;\n        return {\n          days: getDaysInMonth(new Date(yearObj.year, month, 1)),\n        };\n      }),\n    }));\n  }\n\n  return data;\n};\n\nconst getOffset = (\n  date: Date,\n  timelineStartDate: Date,\n  context: GanttContextProps\n) => {\n  const parsedColumnWidth = (context.columnWidth * context.zoom) / 100;\n  const differenceIn = getDifferenceIn(context.range);\n  const startOf = getStartOf(context.range);\n  const fullColumns = differenceIn(startOf(date), timelineStartDate);\n\n  if (context.range === \"daily\") {\n    return parsedColumnWidth * fullColumns;\n  }\n\n  const partialColumns = date.getDate();\n  const daysInMonth = getDaysInMonth(date);\n  const pixelsPerDay = parsedColumnWidth / daysInMonth;\n\n  return fullColumns * parsedColumnWidth + partialColumns * pixelsPerDay;\n};\n\nconst getWidth = (\n  startAt: Date,\n  endAt: Date | null,\n  context: GanttContextProps\n) => {\n  const parsedColumnWidth = (context.columnWidth * context.zoom) / 100;\n\n  if (!endAt) {\n    return parsedColumnWidth * 2;\n  }\n\n  const differenceIn = getDifferenceIn(context.range);\n\n  if (context.range === \"daily\") {\n    const delta = differenceIn(endAt, startAt);\n\n    return parsedColumnWidth * (delta ? delta : 1);\n  }\n\n  const daysInStartMonth = getDaysInMonth(startAt);\n  const pixelsPerDayInStartMonth = parsedColumnWidth / daysInStartMonth;\n\n  if (isSameDay(startAt, endAt)) {\n    return pixelsPerDayInStartMonth;\n  }\n\n  const innerDifferenceIn = getInnerDifferenceIn(context.range);\n  const startOf = getStartOf(context.range);\n\n  if (isSameDay(startOf(startAt), startOf(endAt))) {\n    return innerDifferenceIn(endAt, startAt) * pixelsPerDayInStartMonth;\n  }\n\n  const startRangeOffset = daysInStartMonth - getDate(startAt);\n  const endRangeOffset = getDate(endAt);\n  const fullRangeOffset = differenceIn(startOf(endAt), startOf(startAt));\n  const daysInEndMonth = getDaysInMonth(endAt);\n  const pixelsPerDayInEndMonth = parsedColumnWidth / daysInEndMonth;\n\n  return (\n    (fullRangeOffset - 1) * parsedColumnWidth +\n    startRangeOffset * pixelsPerDayInStartMonth +\n    endRangeOffset * pixelsPerDayInEndMonth\n  );\n};\n\nconst calculateInnerOffset = (\n  date: Date,\n  range: Range,\n  columnWidth: number\n) => {\n  const startOf = getStartOf(range);\n  const endOf = getEndOf(range);\n  const differenceIn = getInnerDifferenceIn(range);\n  const startOfRange = startOf(date);\n  const endOfRange = endOf(date);\n  const totalRangeDays = differenceIn(endOfRange, startOfRange);\n  const dayOfMonth = date.getDate();\n\n  return (dayOfMonth / totalRangeDays) * columnWidth;\n};\n\nconst GanttContext = createContext<GanttContextProps>({\n  zoom: 100,\n  range: \"monthly\",\n  columnWidth: 50,\n  headerHeight: 60,\n  sidebarWidth: 300,\n  rowHeight: 36,\n  onAddItem: undefined,\n  placeholderLength: 2,\n  timelineData: [],\n  ref: null,\n  scrollToFeature: undefined,\n});\n\nexport type GanttContentHeaderProps = {\n  renderHeaderItem: (index: number) => ReactNode;\n  title: string;\n  columns: number;\n};\n\nexport const GanttContentHeader: FC<GanttContentHeaderProps> = ({\n  title,\n  columns,\n  renderHeaderItem,\n}) => {\n  const id = useId();\n\n  return (\n    <div\n      className=\"sticky top-0 z-20 grid w-full shrink-0 bg-backdrop/90 backdrop-blur-sm\"\n      style={{ height: \"var(--gantt-header-height)\" }}\n    >\n      <div>\n        <div\n          className=\"sticky inline-flex whitespace-nowrap px-3 py-2 text-muted-foreground text-xs\"\n          style={{\n            left: \"var(--gantt-sidebar-width)\",\n          }}\n        >\n          <p>{title}</p>\n        </div>\n      </div>\n      <div\n        className=\"grid w-full\"\n        style={{\n          gridTemplateColumns: `repeat(${columns}, var(--gantt-column-width))`,\n        }}\n      >\n        {Array.from({ length: columns }).map((_, index) => (\n          <div\n            className=\"shrink-0 border-border/50 border-b py-1 text-center text-xs\"\n            key={`${id}-${index}`}\n          >\n            {renderHeaderItem(index)}\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n};\n\nconst DailyHeader: FC = () => {\n  const gantt = useContext(GanttContext);\n\n  return gantt.timelineData.map((year) =>\n    year.quarters\n      .flatMap((quarter) => quarter.months)\n      .map((month, index) => (\n        <div className=\"relative flex flex-col\" key={`${year.year}-${index}`}>\n          <GanttContentHeader\n            columns={month.days}\n            renderHeaderItem={(item: number) => (\n              <div className=\"flex items-center justify-center gap-1\">\n                <p>\n                  {format(addDays(new Date(year.year, index, 1), item), \"d\")}\n                </p>\n                <p className=\"text-muted-foreground\">\n                  {format(\n                    addDays(new Date(year.year, index, 1), item),\n                    \"EEEEE\"\n                  )}\n                </p>\n              </div>\n            )}\n            title={format(new Date(year.year, index, 1), \"MMMM yyyy\")}\n          />\n          <GanttColumns\n            columns={month.days}\n            isColumnSecondary={(item: number) =>\n              [0, 6].includes(\n                addDays(new Date(year.year, index, 1), item).getDay()\n              )\n            }\n          />\n        </div>\n      ))\n  );\n};\n\nconst MonthlyHeader: FC = () => {\n  const gantt = useContext(GanttContext);\n\n  return gantt.timelineData.map((year) => (\n    <div className=\"relative flex flex-col\" key={year.year}>\n      <GanttContentHeader\n        columns={year.quarters.flatMap((quarter) => quarter.months).length}\n        renderHeaderItem={(item: number) => (\n          <p>{format(new Date(year.year, item, 1), \"MMM\")}</p>\n        )}\n        title={`${year.year}`}\n      />\n      <GanttColumns\n        columns={year.quarters.flatMap((quarter) => quarter.months).length}\n      />\n    </div>\n  ));\n};\n\nconst QuarterlyHeader: FC = () => {\n  const gantt = useContext(GanttContext);\n\n  return gantt.timelineData.map((year) =>\n    year.quarters.map((quarter, quarterIndex) => (\n      <div\n        className=\"relative flex flex-col\"\n        key={`${year.year}-${quarterIndex}`}\n      >\n        <GanttContentHeader\n          columns={quarter.months.length}\n          renderHeaderItem={(item: number) => (\n            <p>\n              {format(new Date(year.year, quarterIndex * 3 + item, 1), \"MMM\")}\n            </p>\n          )}\n          title={`Q${quarterIndex + 1} ${year.year}`}\n        />\n        <GanttColumns columns={quarter.months.length} />\n      </div>\n    ))\n  );\n};\n\nconst headers: Record<Range, FC> = {\n  daily: DailyHeader,\n  monthly: MonthlyHeader,\n  quarterly: QuarterlyHeader,\n};\n\nexport type GanttHeaderProps = {\n  className?: string;\n};\n\nexport const GanttHeader: FC<GanttHeaderProps> = ({ className }) => {\n  const gantt = useContext(GanttContext);\n  const Header = headers[gantt.range];\n\n  return (\n    <div\n      className={cn(\n        \"-space-x-px flex h-full w-max divide-x divide-border/50\",\n        className\n      )}\n    >\n      <Header />\n    </div>\n  );\n};\n\nexport type GanttSidebarItemProps = {\n  feature: GanttFeature;\n  onSelectItem?: (id: string) => void;\n  className?: string;\n};\n\nexport const GanttSidebarItem: FC<GanttSidebarItemProps> = ({\n  feature,\n  onSelectItem,\n  className,\n}) => {\n  const gantt = useContext(GanttContext);\n  const tempEndAt =\n    feature.endAt && isSameDay(feature.startAt, feature.endAt)\n      ? addDays(feature.endAt, 1)\n      : feature.endAt;\n  const duration = tempEndAt\n    ? formatDistance(feature.startAt, tempEndAt)\n    : `${formatDistance(feature.startAt, new Date())} so far`;\n\n  const handleClick: MouseEventHandler<HTMLDivElement> = (event) => {\n    if (event.target === event.currentTarget) {\n      // Scroll to the feature in the timeline\n      gantt.scrollToFeature?.(feature);\n      // Call the original onSelectItem callback\n      onSelectItem?.(feature.id);\n    }\n  };\n\n  const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = (event) => {\n    if (event.key === \"Enter\") {\n      // Scroll to the feature in the timeline\n      gantt.scrollToFeature?.(feature);\n      // Call the original onSelectItem callback\n      onSelectItem?.(feature.id);\n    }\n  };\n\n  return (\n    <div\n      className={cn(\n        \"relative flex items-center gap-2.5 p-2.5 text-xs hover:bg-secondary\",\n        className\n      )}\n      key={feature.id}\n      onClick={handleClick}\n      onKeyDown={handleKeyDown}\n      // biome-ignore lint/a11y/useSemanticElements: \"This is a clickable item\"\n      role=\"button\"\n      style={{\n        height: \"var(--gantt-row-height)\",\n      }}\n      tabIndex={0}\n    >\n      {/* <Checkbox onCheckedChange={handleCheck} className=\"shrink-0\" /> */}\n      <div\n        className=\"pointer-events-none h-2 w-2 shrink-0 rounded-full\"\n        style={{\n          backgroundColor: feature.status.color,\n        }}\n      />\n      <p className=\"pointer-events-none flex-1 truncate text-left font-medium\">\n        {feature.name}\n      </p>\n      <p className=\"pointer-events-none text-muted-foreground\">{duration}</p>\n    </div>\n  );\n};\n\nexport const GanttSidebarHeader: FC = () => (\n  <div\n    className=\"sticky top-0 z-10 flex shrink-0 items-end justify-between gap-2.5 border-border/50 border-b bg-backdrop/90 p-2.5 font-medium text-muted-foreground text-xs backdrop-blur-sm\"\n    style={{ height: \"var(--gantt-header-height)\" }}\n  >\n    {/* <Checkbox className=\"shrink-0\" /> */}\n    <p className=\"flex-1 truncate text-left\">Issues</p>\n    <p className=\"shrink-0\">Duration</p>\n  </div>\n);\n\nexport type GanttSidebarGroupProps = {\n  children: ReactNode;\n  name: string;\n  className?: string;\n};\n\nexport const GanttSidebarGroup: FC<GanttSidebarGroupProps> = ({\n  children,\n  name,\n  className,\n}) => (\n  <div className={className}>\n    <p\n      className=\"w-full truncate p-2.5 text-left font-medium text-muted-foreground text-xs\"\n      style={{ height: \"var(--gantt-row-height)\" }}\n    >\n      {name}\n    </p>\n    <div className=\"divide-y divide-border/50\">{children}</div>\n  </div>\n);\n\nexport type GanttSidebarProps = {\n  children: ReactNode;\n  className?: string;\n};\n\nexport const GanttSidebar: FC<GanttSidebarProps> = ({\n  children,\n  className,\n}) => (\n  <div\n    className={cn(\n      \"sticky left-0 z-30 h-max min-h-full overflow-clip border-border/50 border-r bg-background/90 backdrop-blur-md\",\n      className\n    )}\n    data-roadmap-ui=\"gantt-sidebar\"\n  >\n    <GanttSidebarHeader />\n    <div className=\"space-y-4\">{children}</div>\n  </div>\n);\n\nexport type GanttAddFeatureHelperProps = {\n  top: number;\n  className?: string;\n};\n\nexport const GanttAddFeatureHelper: FC<GanttAddFeatureHelperProps> = ({\n  top,\n  className,\n}) => {\n  const [scrollX] = useGanttScrollX();\n  const gantt = useContext(GanttContext);\n  const [mousePosition, mouseRef] = useMouse<HTMLDivElement>();\n\n  const handleClick = () => {\n    const ganttRect = gantt.ref?.current?.getBoundingClientRect();\n    const x =\n      mousePosition.x - (ganttRect?.left ?? 0) + scrollX - gantt.sidebarWidth;\n    const currentDate = getDateByMousePosition(gantt, x);\n\n    gantt.onAddItem?.(currentDate);\n  };\n\n  return (\n    <div\n      className={cn(\"absolute top-0 w-full px-0.5\", className)}\n      ref={mouseRef}\n      style={{\n        marginTop: -gantt.rowHeight / 2,\n        transform: `translateY(${top}px)`,\n      }}\n    >\n      <button\n        className=\"flex h-full w-full items-center justify-center rounded-md border border-dashed p-2\"\n        onClick={handleClick}\n        type=\"button\"\n      >\n        <PlusIcon\n          className=\"pointer-events-none select-none text-muted-foreground\"\n          size={16}\n        />\n      </button>\n    </div>\n  );\n};\n\nexport type GanttColumnProps = {\n  index: number;\n  isColumnSecondary?: (item: number) => boolean;\n};\n\nexport const GanttColumn: FC<GanttColumnProps> = ({\n  index,\n  isColumnSecondary,\n}) => {\n  const gantt = useContext(GanttContext);\n  const [dragging] = useGanttDragging();\n  const [mousePosition, mouseRef] = useMouse<HTMLDivElement>();\n  const [hovering, setHovering] = useState(false);\n  const [windowScroll] = useWindowScroll();\n\n  const handleMouseEnter = () => setHovering(true);\n  const handleMouseLeave = () => setHovering(false);\n\n  const top = useThrottle(\n    mousePosition.y -\n      (mouseRef.current?.getBoundingClientRect().y ?? 0) -\n      (windowScroll.y ?? 0),\n    10\n  );\n\n  return (\n    // biome-ignore lint/a11y/noStaticElementInteractions: \"This is a clickable column\"\n    // biome-ignore lint/nursery/noNoninteractiveElementInteractions: \"This is a clickable column\"\n    <div\n      className={cn(\n        \"group relative h-full overflow-hidden\",\n        isColumnSecondary?.(index) ? \"bg-secondary\" : \"\"\n      )}\n      onMouseEnter={handleMouseEnter}\n      onMouseLeave={handleMouseLeave}\n      ref={mouseRef}\n    >\n      {!dragging && hovering && gantt.onAddItem ? (\n        <GanttAddFeatureHelper top={top} />\n      ) : null}\n    </div>\n  );\n};\n\nexport type GanttColumnsProps = {\n  columns: number;\n  isColumnSecondary?: (item: number) => boolean;\n};\n\nexport const GanttColumns: FC<GanttColumnsProps> = ({\n  columns,\n  isColumnSecondary,\n}) => {\n  const id = useId();\n\n  return (\n    <div\n      className=\"divide grid h-full w-full divide-x divide-border/50\"\n      style={{\n        gridTemplateColumns: `repeat(${columns}, var(--gantt-column-width))`,\n      }}\n    >\n      {Array.from({ length: columns }).map((_, index) => (\n        <GanttColumn\n          index={index}\n          isColumnSecondary={isColumnSecondary}\n          key={`${id}-${index}`}\n        />\n      ))}\n    </div>\n  );\n};\n\nexport type GanttCreateMarkerTriggerProps = {\n  onCreateMarker: (date: Date) => void;\n  className?: string;\n};\n\nexport const GanttCreateMarkerTrigger: FC<GanttCreateMarkerTriggerProps> = ({\n  onCreateMarker,\n  className,\n}) => {\n  const gantt = useContext(GanttContext);\n  const [mousePosition, mouseRef] = useMouse<HTMLDivElement>();\n  const [windowScroll] = useWindowScroll();\n  const x = useThrottle(\n    mousePosition.x -\n      (mouseRef.current?.getBoundingClientRect().x ?? 0) -\n      (windowScroll.x ?? 0),\n    10\n  );\n\n  const date = getDateByMousePosition(gantt, x);\n\n  const handleClick = () => onCreateMarker(date);\n\n  return (\n    <div\n      className={cn(\n        \"group pointer-events-none absolute top-0 left-0 h-full w-full select-none overflow-visible\",\n        className\n      )}\n      ref={mouseRef}\n    >\n      <div\n        className=\"-ml-2 pointer-events-auto sticky top-6 z-20 flex w-4 flex-col items-center justify-center gap-1 overflow-visible opacity-0 group-hover:opacity-100\"\n        style={{ transform: `translateX(${x}px)` }}\n      >\n        <button\n          className=\"z-50 inline-flex h-4 w-4 items-center justify-center rounded-full bg-card\"\n          onClick={handleClick}\n          type=\"button\"\n        >\n          <PlusIcon className=\"text-muted-foreground\" size={12} />\n        </button>\n        <div className=\"whitespace-nowrap rounded-full border border-border/50 bg-background/90 px-2 py-1 text-foreground text-xs backdrop-blur-lg\">\n          {formatDate(date, \"MMM dd, yyyy\")}\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport type GanttFeatureDragHelperProps = {\n  featureId: GanttFeature[\"id\"];\n  direction: \"left\" | \"right\";\n  date: Date | null;\n};\n\nexport const GanttFeatureDragHelper: FC<GanttFeatureDragHelperProps> = ({\n  direction,\n  featureId,\n  date,\n}) => {\n  const [, setDragging] = useGanttDragging();\n  const { attributes, listeners, setNodeRef } = useDraggable({\n    id: `feature-drag-helper-${featureId}`,\n  });\n\n  const isPressed = Boolean(attributes[\"aria-pressed\"]);\n\n  useEffect(() => setDragging(isPressed), [isPressed, setDragging]);\n\n  return (\n    <div\n      className={cn(\n        \"group -translate-y-1/2 !cursor-col-resize absolute top-1/2 z-[3] h-full w-6 rounded-md outline-none\",\n        direction === \"left\" ? \"-left-2.5\" : \"-right-2.5\"\n      )}\n      ref={setNodeRef}\n      {...attributes}\n      {...listeners}\n    >\n      <div\n        className={cn(\n          \"-translate-y-1/2 absolute top-1/2 h-[80%] w-1 rounded-sm bg-muted-foreground opacity-0 transition-all\",\n          direction === \"left\" ? \"left-2.5\" : \"right-2.5\",\n          direction === \"left\" ? \"group-hover:left-0\" : \"group-hover:right-0\",\n          isPressed && (direction === \"left\" ? \"left-0\" : \"right-0\"),\n          \"group-hover:opacity-100\",\n          isPressed && \"opacity-100\"\n        )}\n      />\n      {date && (\n        <div\n          className={cn(\n            \"-translate-x-1/2 absolute top-10 hidden whitespace-nowrap rounded-lg border border-border/50 bg-background/90 px-2 py-1 text-foreground text-xs backdrop-blur-lg group-hover:block\",\n            isPressed && \"block\"\n          )}\n        >\n          {format(date, \"MMM dd, yyyy\")}\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport type GanttFeatureItemCardProps = Pick<GanttFeature, \"id\"> & {\n  children?: ReactNode;\n};\n\nexport const GanttFeatureItemCard: FC<GanttFeatureItemCardProps> = ({\n  id,\n  children,\n}) => {\n  const [, setDragging] = useGanttDragging();\n  const { attributes, listeners, setNodeRef } = useDraggable({ id });\n  const isPressed = Boolean(attributes[\"aria-pressed\"]);\n\n  useEffect(() => setDragging(isPressed), [isPressed, setDragging]);\n\n  return (\n    <Card className=\"h-full w-full rounded-md bg-background p-2 text-xs shadow-sm\">\n      <div\n        className={cn(\n          \"flex h-full w-full items-center justify-between gap-2 text-left\",\n          isPressed && \"cursor-grabbing\"\n        )}\n        {...attributes}\n        {...listeners}\n        ref={setNodeRef}\n      >\n        {children}\n      </div>\n    </Card>\n  );\n};\n\nexport type GanttFeatureItemProps = GanttFeature & {\n  onMove?: (id: string, startDate: Date, endDate: Date | null) => void;\n  children?: ReactNode;\n  className?: string;\n};\n\nexport const GanttFeatureItem: FC<GanttFeatureItemProps> = ({\n  onMove,\n  children,\n  className,\n  ...feature\n}) => {\n  const [scrollX] = useGanttScrollX();\n  const gantt = useContext(GanttContext);\n  const timelineStartDate = useMemo(\n    () => new Date(gantt.timelineData.at(0)?.year ?? 0, 0, 1),\n    [gantt.timelineData]\n  );\n  const [startAt, setStartAt] = useState<Date>(feature.startAt);\n  const [endAt, setEndAt] = useState<Date | null>(feature.endAt);\n\n  // Memoize expensive calculations\n  const width = useMemo(\n    () => getWidth(startAt, endAt, gantt),\n    [startAt, endAt, gantt]\n  );\n  const offset = useMemo(\n    () => getOffset(startAt, timelineStartDate, gantt),\n    [startAt, timelineStartDate, gantt]\n  );\n\n  const addRange = useMemo(() => getAddRange(gantt.range), [gantt.range]);\n  const [mousePosition] = useMouse<HTMLDivElement>();\n\n  const [previousMouseX, setPreviousMouseX] = useState(0);\n  const [previousStartAt, setPreviousStartAt] = useState(startAt);\n  const [previousEndAt, setPreviousEndAt] = useState(endAt);\n\n  const mouseSensor = useSensor(MouseSensor, {\n    activationConstraint: {\n      distance: 10,\n    },\n  });\n\n  const handleItemDragStart = useCallback(() => {\n    setPreviousMouseX(mousePosition.x);\n    setPreviousStartAt(startAt);\n    setPreviousEndAt(endAt);\n  }, [mousePosition.x, startAt, endAt]);\n\n  const handleItemDragMove = useCallback(() => {\n    const currentDate = getDateByMousePosition(gantt, mousePosition.x);\n    const originalDate = getDateByMousePosition(gantt, previousMouseX);\n    const delta =\n      gantt.range === \"daily\"\n        ? getDifferenceIn(gantt.range)(currentDate, originalDate)\n        : getInnerDifferenceIn(gantt.range)(currentDate, originalDate);\n    const newStartDate = addDays(previousStartAt, delta);\n    const newEndDate = previousEndAt ? addDays(previousEndAt, delta) : null;\n\n    setStartAt(newStartDate);\n    setEndAt(newEndDate);\n  }, [gantt, mousePosition.x, previousMouseX, previousStartAt, previousEndAt]);\n\n  const onDragEnd = useCallback(\n    () => onMove?.(feature.id, startAt, endAt),\n    [onMove, feature.id, startAt, endAt]\n  );\n\n  const handleLeftDragMove = useCallback(() => {\n    const ganttRect = gantt.ref?.current?.getBoundingClientRect();\n    const x =\n      mousePosition.x - (ganttRect?.left ?? 0) + scrollX - gantt.sidebarWidth;\n    const newStartAt = getDateByMousePosition(gantt, x);\n\n    setStartAt(newStartAt);\n  }, [gantt, mousePosition.x, scrollX]);\n\n  const handleRightDragMove = useCallback(() => {\n    const ganttRect = gantt.ref?.current?.getBoundingClientRect();\n    const x =\n      mousePosition.x - (ganttRect?.left ?? 0) + scrollX - gantt.sidebarWidth;\n    const newEndAt = getDateByMousePosition(gantt, x);\n\n    setEndAt(newEndAt);\n  }, [gantt, mousePosition.x, scrollX]);\n\n  return (\n    <div\n      className={cn(\"relative flex w-max min-w-full py-0.5\", className)}\n      style={{ height: \"var(--gantt-row-height)\" }}\n    >\n      <div\n        className=\"pointer-events-auto absolute top-0.5\"\n        style={{\n          height: \"calc(var(--gantt-row-height) - 4px)\",\n          width: Math.round(width),\n          left: Math.round(offset),\n        }}\n      >\n        {onMove && (\n          <DndContext\n            modifiers={[restrictToHorizontalAxis]}\n            onDragEnd={onDragEnd}\n            onDragMove={handleLeftDragMove}\n            sensors={[mouseSensor]}\n          >\n            <GanttFeatureDragHelper\n              date={startAt}\n              direction=\"left\"\n              featureId={feature.id}\n            />\n          </DndContext>\n        )}\n        <DndContext\n          modifiers={[restrictToHorizontalAxis]}\n          onDragEnd={onDragEnd}\n          onDragMove={handleItemDragMove}\n          onDragStart={handleItemDragStart}\n          sensors={[mouseSensor]}\n        >\n          <GanttFeatureItemCard id={feature.id}>\n            {children ?? (\n              <p className=\"flex-1 truncate text-xs\">{feature.name}</p>\n            )}\n          </GanttFeatureItemCard>\n        </DndContext>\n        {onMove && (\n          <DndContext\n            modifiers={[restrictToHorizontalAxis]}\n            onDragEnd={onDragEnd}\n            onDragMove={handleRightDragMove}\n            sensors={[mouseSensor]}\n          >\n            <GanttFeatureDragHelper\n              date={endAt ?? addRange(startAt, 2)}\n              direction=\"right\"\n              featureId={feature.id}\n            />\n          </DndContext>\n        )}\n      </div>\n    </div>\n  );\n};\n\nexport type GanttFeatureListGroupProps = {\n  children: ReactNode;\n  className?: string;\n};\n\nexport const GanttFeatureListGroup: FC<GanttFeatureListGroupProps> = ({\n  children,\n  className,\n}) => (\n  <div className={className} style={{ paddingTop: \"var(--gantt-row-height)\" }}>\n    {children}\n  </div>\n);\n\nexport type GanttFeatureRowProps = {\n  features: GanttFeature[];\n  onMove?: (id: string, startAt: Date, endAt: Date | null) => void;\n  children?: (feature: GanttFeature) => ReactNode;\n  className?: string;\n};\n\nexport const GanttFeatureRow: FC<GanttFeatureRowProps> = ({\n  features,\n  onMove,\n  children,\n  className,\n}) => {\n  // Sort features by start date to handle potential overlaps\n  const sortedFeatures = [...features].sort(\n    (a, b) => a.startAt.getTime() - b.startAt.getTime()\n  );\n\n  // Calculate sub-row positions for overlapping features using a proper algorithm\n  const featureWithPositions = [];\n  const subRowEndTimes: Date[] = []; // Track when each sub-row becomes free\n\n  for (const feature of sortedFeatures) {\n    let subRow = 0;\n\n    // Find the first sub-row that's free (doesn't overlap)\n    while (\n      subRow < subRowEndTimes.length &&\n      subRowEndTimes[subRow] > feature.startAt\n    ) {\n      subRow++;\n    }\n\n    // Update the end time for this sub-row\n    if (subRow === subRowEndTimes.length) {\n      subRowEndTimes.push(feature.endAt);\n    } else {\n      subRowEndTimes[subRow] = feature.endAt;\n    }\n\n    featureWithPositions.push({ ...feature, subRow });\n  }\n\n  const maxSubRows = Math.max(1, subRowEndTimes.length);\n  const subRowHeight = 36; // Base row height\n\n  return (\n    <div\n      className={cn(\"relative\", className)}\n      style={{\n        height: `${maxSubRows * subRowHeight}px`,\n        minHeight: \"var(--gantt-row-height)\",\n      }}\n    >\n      {featureWithPositions.map((feature) => (\n        <div\n          className=\"absolute w-full\"\n          key={feature.id}\n          style={{\n            top: `${feature.subRow * subRowHeight}px`,\n            height: `${subRowHeight}px`,\n          }}\n        >\n          <GanttFeatureItem {...feature} onMove={onMove}>\n            {children ? (\n              children(feature)\n            ) : (\n              <p className=\"flex-1 truncate text-xs\">{feature.name}</p>\n            )}\n          </GanttFeatureItem>\n        </div>\n      ))}\n    </div>\n  );\n};\n\nexport type GanttFeatureListProps = {\n  className?: string;\n  children: ReactNode;\n};\n\nexport const GanttFeatureList: FC<GanttFeatureListProps> = ({\n  className,\n  children,\n}) => (\n  <div\n    className={cn(\"absolute top-0 left-0 h-full w-max space-y-4\", className)}\n    style={{ marginTop: \"var(--gantt-header-height)\" }}\n  >\n    {children}\n  </div>\n);\n\nexport const GanttMarker: FC<\n  GanttMarkerProps & {\n    onRemove?: (id: string) => void;\n    className?: string;\n  }\n> = memo(({ label, date, id, onRemove, className }) => {\n  const gantt = useContext(GanttContext);\n  const differenceIn = useMemo(\n    () => getDifferenceIn(gantt.range),\n    [gantt.range]\n  );\n  const timelineStartDate = useMemo(\n    () => new Date(gantt.timelineData.at(0)?.year ?? 0, 0, 1),\n    [gantt.timelineData]\n  );\n\n  // Memoize expensive calculations\n  const offset = useMemo(\n    () => differenceIn(date, timelineStartDate),\n    [differenceIn, date, timelineStartDate]\n  );\n  const innerOffset = useMemo(\n    () =>\n      calculateInnerOffset(\n        date,\n        gantt.range,\n        (gantt.columnWidth * gantt.zoom) / 100\n      ),\n    [date, gantt.range, gantt.columnWidth, gantt.zoom]\n  );\n\n  const handleRemove = useCallback(() => onRemove?.(id), [onRemove, id]);\n\n  return (\n    <div\n      className=\"pointer-events-none absolute top-0 left-0 z-20 flex h-full select-none flex-col items-center justify-center overflow-visible\"\n      style={{\n        width: 0,\n        transform: `translateX(calc(var(--gantt-column-width) * ${offset} + ${innerOffset}px))`,\n      }}\n    >\n      <ContextMenu>\n        <ContextMenuTrigger asChild>\n          <div\n            className={cn(\n              \"group pointer-events-auto sticky top-0 flex select-auto flex-col flex-nowrap items-center justify-center whitespace-nowrap rounded-b-md bg-card px-2 py-1 text-foreground text-xs\",\n              className\n            )}\n          >\n            {label}\n            <span className=\"max-h-[0] overflow-hidden opacity-80 transition-all group-hover:max-h-[2rem]\">\n              {formatDate(date, \"MMM dd, yyyy\")}\n            </span>\n          </div>\n        </ContextMenuTrigger>\n        <ContextMenuContent>\n          {onRemove ? (\n            <ContextMenuItem\n              className=\"flex items-center gap-2 text-destructive\"\n              onClick={handleRemove}\n            >\n              <TrashIcon size={16} />\n              Remove marker\n            </ContextMenuItem>\n          ) : null}\n        </ContextMenuContent>\n      </ContextMenu>\n      <div className={cn(\"h-full w-px bg-card\", className)} />\n    </div>\n  );\n});\n\nGanttMarker.displayName = \"GanttMarker\";\n\nexport type GanttProviderProps = {\n  range?: Range;\n  zoom?: number;\n  onAddItem?: (date: Date) => void;\n  children: ReactNode;\n  className?: string;\n};\n\nexport const GanttProvider: FC<GanttProviderProps> = ({\n  zoom = 100,\n  range = \"monthly\",\n  onAddItem,\n  children,\n  className,\n}) => {\n  const scrollRef = useRef<HTMLDivElement>(null);\n  const [timelineData, setTimelineData] = useState<TimelineData>(\n    createInitialTimelineData(new Date())\n  );\n  const [, setScrollX] = useGanttScrollX();\n  const [sidebarWidth, setSidebarWidth] = useState(0);\n\n  const headerHeight = 60;\n  const rowHeight = 36;\n  let columnWidth = 50;\n\n  if (range === \"monthly\") {\n    columnWidth = 150;\n  } else if (range === \"quarterly\") {\n    columnWidth = 100;\n  }\n\n  // Memoize CSS variables to prevent unnecessary re-renders\n  const cssVariables = useMemo(\n    () =>\n      ({\n        \"--gantt-zoom\": `${zoom}`,\n        \"--gantt-column-width\": `${(zoom / 100) * columnWidth}px`,\n        \"--gantt-header-height\": `${headerHeight}px`,\n        \"--gantt-row-height\": `${rowHeight}px`,\n        \"--gantt-sidebar-width\": `${sidebarWidth}px`,\n      }) as CSSProperties,\n    [zoom, columnWidth, sidebarWidth]\n  );\n\n  useEffect(() => {\n    if (scrollRef.current) {\n      scrollRef.current.scrollLeft =\n        scrollRef.current.scrollWidth / 2 - scrollRef.current.clientWidth / 2;\n      setScrollX(scrollRef.current.scrollLeft);\n    }\n  }, [setScrollX]);\n\n  // Update sidebar width when DOM is ready\n  useEffect(() => {\n    const updateSidebarWidth = () => {\n      const sidebarElement = scrollRef.current?.querySelector(\n        '[data-roadmap-ui=\"gantt-sidebar\"]'\n      );\n      const newWidth = sidebarElement ? 300 : 0;\n      setSidebarWidth(newWidth);\n    };\n\n    // Update immediately\n    updateSidebarWidth();\n\n    // Also update on resize or when children change\n    const observer = new MutationObserver(updateSidebarWidth);\n    if (scrollRef.current) {\n      observer.observe(scrollRef.current, {\n        childList: true,\n        subtree: true,\n      });\n    }\n\n    return () => {\n      observer.disconnect();\n    };\n  }, []);\n\n  // Fix the useCallback to include all dependencies\n  const handleScroll = useCallback(\n    throttle(() => {\n      const scrollElement = scrollRef.current;\n      if (!scrollElement) {\n        return;\n      }\n\n      const { scrollLeft, scrollWidth, clientWidth } = scrollElement;\n      setScrollX(scrollLeft);\n\n      if (scrollLeft === 0) {\n        // Extend timelineData to the past\n        const firstYear = timelineData[0]?.year;\n\n        if (!firstYear) {\n          return;\n        }\n\n        const newTimelineData: TimelineData = [...timelineData];\n        newTimelineData.unshift({\n          year: firstYear - 1,\n          quarters: new Array(4).fill(null).map((_, quarterIndex) => ({\n            months: new Array(3).fill(null).map((_, monthIndex) => {\n              const month = quarterIndex * 3 + monthIndex;\n              return {\n                days: getDaysInMonth(new Date(firstYear, month, 1)),\n              };\n            }),\n          })),\n        });\n\n        setTimelineData(newTimelineData);\n\n        // Scroll a bit forward so it's not at the very start\n        scrollElement.scrollLeft = scrollElement.clientWidth;\n        setScrollX(scrollElement.scrollLeft);\n      } else if (scrollLeft + clientWidth >= scrollWidth) {\n        // Extend timelineData to the future\n        const lastYear = timelineData.at(-1)?.year;\n\n        if (!lastYear) {\n          return;\n        }\n\n        const newTimelineData: TimelineData = [...timelineData];\n        newTimelineData.push({\n          year: lastYear + 1,\n          quarters: new Array(4).fill(null).map((_, quarterIndex) => ({\n            months: new Array(3).fill(null).map((_, monthIndex) => {\n              const month = quarterIndex * 3 + monthIndex;\n              return {\n                days: getDaysInMonth(new Date(lastYear, month, 1)),\n              };\n            }),\n          })),\n        });\n\n        setTimelineData(newTimelineData);\n\n        // Scroll a bit back so it's not at the very end\n        scrollElement.scrollLeft =\n          scrollElement.scrollWidth - scrollElement.clientWidth;\n        setScrollX(scrollElement.scrollLeft);\n      }\n    }, 100),\n    []\n  );\n\n  useEffect(() => {\n    const scrollElement = scrollRef.current;\n    if (scrollElement) {\n      scrollElement.addEventListener(\"scroll\", handleScroll);\n    }\n\n    return () => {\n      // Fix memory leak by properly referencing the scroll element\n      if (scrollElement) {\n        scrollElement.removeEventListener(\"scroll\", handleScroll);\n      }\n    };\n  }, [handleScroll]);\n\n  const scrollToFeature = useCallback(\n    (feature: GanttFeature) => {\n      const scrollElement = scrollRef.current;\n      if (!scrollElement) {\n        return;\n      }\n\n      // Calculate timeline start date from timelineData\n      const timelineStartDate = new Date(timelineData[0].year, 0, 1);\n\n      // Calculate the horizontal offset for the feature's start date\n      const offset = getOffset(feature.startAt, timelineStartDate, {\n        zoom,\n        range,\n        columnWidth,\n        sidebarWidth,\n        headerHeight,\n        rowHeight,\n        onAddItem,\n        placeholderLength: 2,\n        timelineData,\n        ref: scrollRef,\n      });\n\n      // Scroll to align the feature's start with the right side of the sidebar\n      const targetScrollLeft = Math.max(0, offset);\n\n      scrollElement.scrollTo({\n        left: targetScrollLeft,\n        behavior: \"smooth\",\n      });\n    },\n    [timelineData, zoom, range, columnWidth, sidebarWidth, onAddItem]\n  );\n\n  return (\n    <GanttContext.Provider\n      value={{\n        zoom,\n        range,\n        headerHeight,\n        columnWidth,\n        sidebarWidth,\n        rowHeight,\n        onAddItem,\n        timelineData,\n        placeholderLength: 2,\n        ref: scrollRef,\n        scrollToFeature,\n      }}\n    >\n      <div\n        className={cn(\n          \"gantt relative isolate grid h-full w-full flex-none select-none overflow-auto rounded-sm bg-secondary\",\n          range,\n          className\n        )}\n        ref={scrollRef}\n        style={{\n          ...cssVariables,\n          gridTemplateColumns: \"var(--gantt-sidebar-width) 1fr\",\n        }}\n      >\n        {children}\n      </div>\n    </GanttContext.Provider>\n  );\n};\n\nexport type GanttTimelineProps = {\n  children: ReactNode;\n  className?: string;\n};\n\nexport const GanttTimeline: FC<GanttTimelineProps> = ({\n  children,\n  className,\n}) => (\n  <div\n    className={cn(\n      \"relative flex h-full w-max flex-none overflow-clip\",\n      className\n    )}\n  >\n    {children}\n  </div>\n);\n\nexport type GanttTodayProps = {\n  className?: string;\n};\n\nexport const GanttToday: FC<GanttTodayProps> = ({ className }) => {\n  const label = \"Today\";\n  const date = useMemo(() => new Date(), []);\n  const gantt = useContext(GanttContext);\n  const differenceIn = useMemo(\n    () => getDifferenceIn(gantt.range),\n    [gantt.range]\n  );\n  const timelineStartDate = useMemo(\n    () => new Date(gantt.timelineData.at(0)?.year ?? 0, 0, 1),\n    [gantt.timelineData]\n  );\n\n  // Memoize expensive calculations\n  const offset = useMemo(\n    () => differenceIn(date, timelineStartDate),\n    [differenceIn, date, timelineStartDate]\n  );\n  const innerOffset = useMemo(\n    () =>\n      calculateInnerOffset(\n        date,\n        gantt.range,\n        (gantt.columnWidth * gantt.zoom) / 100\n      ),\n    [date, gantt.range, gantt.columnWidth, gantt.zoom]\n  );\n\n  return (\n    <div\n      className=\"pointer-events-none absolute top-0 left-0 z-20 flex h-full select-none flex-col items-center justify-center overflow-visible\"\n      style={{\n        width: 0,\n        transform: `translateX(calc(var(--gantt-column-width) * ${offset} + ${innerOffset}px))`,\n      }}\n    >\n      <div\n        className={cn(\n          \"group pointer-events-auto sticky top-0 flex select-auto flex-col flex-nowrap items-center justify-center whitespace-nowrap rounded-b-md bg-card px-2 py-1 text-foreground text-xs\",\n          className\n        )}\n      >\n        {label}\n        <span className=\"max-h-[0] overflow-hidden opacity-80 transition-all group-hover:max-h-[2rem]\">\n          {formatDate(date, \"MMM dd, yyyy\")}\n        </span>\n      </div>\n      <div className={cn(\"h-full w-px bg-card\", className)} />\n    </div>\n  );\n};\n","target":"components/kibo-ui/gantt/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"glimpse","type":"registry:ui","title":"glimpse","description":"A component that shows a preview of a URL when hovering over a link.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":[],"devDependencies":[],"registryDependencies":["hover-card"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { cn } from \"@repo/shadcn-ui/lib/utils\";\nimport type { ComponentProps } from \"react\";\nimport {\n  HoverCard,\n  HoverCardContent,\n  HoverCardTrigger,\n} from \"@/components/ui/hover-card\";\n\nexport type GlimpseProps = ComponentProps<typeof HoverCard>;\n\nexport const Glimpse = (props: GlimpseProps) => {\n  return <HoverCard {...props} />;\n};\n\nexport type GlimpseContentProps = ComponentProps<typeof HoverCardContent>;\n\nexport const GlimpseContent = (props: GlimpseContentProps) => (\n  <HoverCardContent {...props} />\n);\n\nexport type GlimpseTriggerProps = ComponentProps<typeof HoverCardTrigger>;\n\nexport const GlimpseTrigger = (props: GlimpseTriggerProps) => (\n  <HoverCardTrigger {...props} />\n);\n\nexport type GlimpseTitleProps = ComponentProps<\"p\">;\n\nexport const GlimpseTitle = ({ className, ...props }: GlimpseTitleProps) => {\n  return (\n    <p className={cn(\"truncate font-semibold text-sm\", className)} {...props} />\n  );\n};\n\nexport type GlimpseDescriptionProps = ComponentProps<\"p\">;\n\nexport const GlimpseDescription = ({\n  className,\n  ...props\n}: GlimpseDescriptionProps) => {\n  return (\n    <p\n      className={cn(\"line-clamp-2 text-muted-foreground text-sm\", className)}\n      {...props}\n    />\n  );\n};\n\nexport type GlimpseImageProps = ComponentProps<\"img\">;\n\nexport const GlimpseImage = ({\n  className,\n  alt,\n  ...props\n}: GlimpseImageProps) => (\n  // biome-ignore lint/performance/noImgElement: \"Kibo UI is framework agnostic\"\n  <img\n    alt={alt ?? \"\"}\n    className={cn(\n      \"mb-4 aspect-[120/63] w-full rounded-md border object-cover\",\n      className\n    )}\n    {...props}\n  />\n);\n","target":"components/kibo-ui/glimpse/index.tsx"},{"type":"registry:ui","path":"server.tsx","content":"const TITLE_REGEX = /<title[^>]*>([^<]+)<\\/title>/;\nconst OG_TITLE_REGEX = /<meta[^>]*property=\"og:title\"[^>]*content=\"([^\"]+)\"/;\nconst DESCRIPTION_REGEX = /<meta[^>]*name=\"description\"[^>]*content=\"([^\"]+)\"/;\nconst OG_DESCRIPTION_REGEX =\n  /<meta[^>]*property=\"og:description\"[^>]*content=\"([^\"]+)\"/;\nconst OG_IMAGE_REGEX = /<meta[^>]*property=\"og:image\"[^>]*content=\"([^\"]+)\"/;\n\nexport const glimpse = async (url: string) => {\n  const response = await fetch(url);\n  const data = await response.text();\n  const titleMatch = data.match(TITLE_REGEX) || data.match(OG_TITLE_REGEX);\n  const descriptionMatch =\n    data.match(DESCRIPTION_REGEX) || data.match(OG_DESCRIPTION_REGEX);\n  const imageMatch = data.match(OG_IMAGE_REGEX);\n\n  return {\n    title: titleMatch?.at(1) ?? null,\n    description: descriptionMatch?.at(1) ?? null,\n    image: imageMatch?.at(1) ?? null,\n  };\n};\n","target":"components/kibo-ui/glimpse/server.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"image-crop","type":"registry:ui","title":"image-crop","description":"Helps you crop images to a specific size or aspect ratio.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["lucide-react","radix-ui","react-image-crop"],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { Button } from \"@repo/shadcn-ui/components/ui/button\";\nimport { CropIcon, RotateCcwIcon } from \"lucide-react\";\nimport { Slot } from \"radix-ui\";\nimport {\n  type ComponentProps,\n  type CSSProperties,\n  createContext,\n  type MouseEvent,\n  type ReactNode,\n  type RefObject,\n  type SyntheticEvent,\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport ReactCrop, {\n  centerCrop,\n  makeAspectCrop,\n  type PercentCrop,\n  type PixelCrop,\n  type ReactCropProps,\n} from \"react-image-crop\";\nimport { cn } from \"@/lib/utils\";\n\nimport \"react-image-crop/dist/ReactCrop.css\";\n\nconst centerAspectCrop = (\n  mediaWidth: number,\n  mediaHeight: number,\n  aspect: number | undefined\n): PercentCrop =>\n  centerCrop(\n    aspect\n      ? makeAspectCrop(\n          {\n            unit: \"%\",\n            width: 90,\n          },\n          aspect,\n          mediaWidth,\n          mediaHeight\n        )\n      : { x: 0, y: 0, width: 90, height: 90, unit: \"%\" },\n    mediaWidth,\n    mediaHeight\n  );\n\nconst getCroppedPngImage = async (\n  imageSrc: HTMLImageElement,\n  scaleFactor: number,\n  pixelCrop: PixelCrop,\n  maxImageSize: number\n): Promise<string> => {\n  const canvas = document.createElement(\"canvas\");\n  const ctx = canvas.getContext(\"2d\");\n\n  if (!ctx) {\n    throw new Error(\"Context is null, this should never happen.\");\n  }\n\n  const scaleX = imageSrc.naturalWidth / imageSrc.width;\n  const scaleY = imageSrc.naturalHeight / imageSrc.height;\n\n  ctx.imageSmoothingEnabled = false;\n  canvas.width = pixelCrop.width;\n  canvas.height = pixelCrop.height;\n\n  ctx.drawImage(\n    imageSrc,\n    pixelCrop.x * scaleX,\n    pixelCrop.y * scaleY,\n    pixelCrop.width * scaleX,\n    pixelCrop.height * scaleY,\n    0,\n    0,\n    canvas.width,\n    canvas.height\n  );\n\n  const croppedImageUrl = canvas.toDataURL(\"image/png\");\n  const response = await fetch(croppedImageUrl);\n  const blob = await response.blob();\n\n  if (blob.size > maxImageSize) {\n    return await getCroppedPngImage(\n      imageSrc,\n      scaleFactor * 0.9,\n      pixelCrop,\n      maxImageSize\n    );\n  }\n\n  return croppedImageUrl;\n};\n\ntype ImageCropContextType = {\n  file: File;\n  maxImageSize: number;\n  imgSrc: string;\n  crop: PercentCrop | undefined;\n  completedCrop: PixelCrop | null;\n  imgRef: RefObject<HTMLImageElement | null>;\n  onCrop?: (croppedImage: string) => void;\n  reactCropProps: Omit<ReactCropProps, \"onChange\" | \"onComplete\" | \"children\">;\n  handleChange: (pixelCrop: PixelCrop, percentCrop: PercentCrop) => void;\n  handleComplete: (\n    pixelCrop: PixelCrop,\n    percentCrop: PercentCrop\n  ) => Promise<void>;\n  onImageLoad: (e: SyntheticEvent<HTMLImageElement>) => void;\n  applyCrop: () => Promise<void>;\n  resetCrop: () => void;\n};\n\nconst ImageCropContext = createContext<ImageCropContextType | null>(null);\n\nconst useImageCrop = () => {\n  const context = useContext(ImageCropContext);\n  if (!context) {\n    throw new Error(\"ImageCrop components must be used within ImageCrop\");\n  }\n  return context;\n};\n\nexport type ImageCropProps = {\n  file: File;\n  maxImageSize?: number;\n  onCrop?: (croppedImage: string) => void;\n  children: ReactNode;\n  onChange?: ReactCropProps[\"onChange\"];\n  onComplete?: ReactCropProps[\"onComplete\"];\n} & Omit<ReactCropProps, \"onChange\" | \"onComplete\" | \"children\">;\n\nexport const ImageCrop = ({\n  file,\n  maxImageSize = 1024 * 1024 * 5,\n  onCrop,\n  children,\n  onChange,\n  onComplete,\n  ...reactCropProps\n}: ImageCropProps) => {\n  const imgRef = useRef<HTMLImageElement | null>(null);\n  const [imgSrc, setImgSrc] = useState<string>(\"\");\n  const [crop, setCrop] = useState<PercentCrop>();\n  const [completedCrop, setCompletedCrop] = useState<PixelCrop | null>(null);\n  const [initialCrop, setInitialCrop] = useState<PercentCrop>();\n\n  useEffect(() => {\n    const reader = new FileReader();\n    reader.addEventListener(\"load\", () =>\n      setImgSrc(reader.result?.toString() || \"\")\n    );\n    reader.readAsDataURL(file);\n  }, [file]);\n\n  const onImageLoad = useCallback(\n    (e: SyntheticEvent<HTMLImageElement>) => {\n      const { width, height } = e.currentTarget;\n      const newCrop = centerAspectCrop(width, height, reactCropProps.aspect);\n      setCrop(newCrop);\n      setInitialCrop(newCrop);\n    },\n    [reactCropProps.aspect]\n  );\n\n  const handleChange = (pixelCrop: PixelCrop, percentCrop: PercentCrop) => {\n    setCrop(percentCrop);\n    onChange?.(pixelCrop, percentCrop);\n  };\n\n  // biome-ignore lint/suspicious/useAwait: \"onComplete is async\"\n  const handleComplete = async (\n    pixelCrop: PixelCrop,\n    percentCrop: PercentCrop\n  ) => {\n    setCompletedCrop(pixelCrop);\n    onComplete?.(pixelCrop, percentCrop);\n  };\n\n  const applyCrop = async () => {\n    if (!(imgRef.current && completedCrop)) {\n      return;\n    }\n\n    const croppedImage = await getCroppedPngImage(\n      imgRef.current,\n      1,\n      completedCrop,\n      maxImageSize\n    );\n\n    onCrop?.(croppedImage);\n  };\n\n  const resetCrop = () => {\n    if (initialCrop) {\n      setCrop(initialCrop);\n      setCompletedCrop(null);\n    }\n  };\n\n  const contextValue: ImageCropContextType = {\n    file,\n    maxImageSize,\n    imgSrc,\n    crop,\n    completedCrop,\n    imgRef,\n    onCrop,\n    reactCropProps,\n    handleChange,\n    handleComplete,\n    onImageLoad,\n    applyCrop,\n    resetCrop,\n  };\n\n  return (\n    <ImageCropContext.Provider value={contextValue}>\n      {children}\n    </ImageCropContext.Provider>\n  );\n};\n\nexport type ImageCropContentProps = {\n  style?: CSSProperties;\n  className?: string;\n};\n\nexport const ImageCropContent = ({\n  style,\n  className,\n}: ImageCropContentProps) => {\n  const {\n    imgSrc,\n    crop,\n    handleChange,\n    handleComplete,\n    onImageLoad,\n    imgRef,\n    reactCropProps,\n  } = useImageCrop();\n\n  const shadcnStyle = {\n    \"--rc-border-color\": \"var(--color-border)\",\n    \"--rc-focus-color\": \"var(--color-primary)\",\n  } as CSSProperties;\n\n  return (\n    <ReactCrop\n      className={cn(\"max-h-[277px] max-w-full\", className)}\n      crop={crop}\n      onChange={handleChange}\n      onComplete={handleComplete}\n      style={{ ...shadcnStyle, ...style }}\n      {...reactCropProps}\n    >\n      {imgSrc && (\n        <img\n          alt=\"crop\"\n          className=\"size-full\"\n          onLoad={onImageLoad}\n          ref={imgRef}\n          src={imgSrc}\n        />\n      )}\n    </ReactCrop>\n  );\n};\n\nexport type ImageCropApplyProps = ComponentProps<\"button\"> & {\n  asChild?: boolean;\n};\n\nexport const ImageCropApply = ({\n  asChild = false,\n  children,\n  onClick,\n  ...props\n}: ImageCropApplyProps) => {\n  const { applyCrop } = useImageCrop();\n\n  const handleClick = async (e: MouseEvent<HTMLButtonElement>) => {\n    await applyCrop();\n    onClick?.(e);\n  };\n\n  if (asChild) {\n    return (\n      <Slot.Root onClick={handleClick} {...props}>\n        {children}\n      </Slot.Root>\n    );\n  }\n\n  return (\n    <Button onClick={handleClick} size=\"icon\" variant=\"ghost\" {...props}>\n      {children ?? <CropIcon className=\"size-4\" />}\n    </Button>\n  );\n};\n\nexport type ImageCropResetProps = ComponentProps<\"button\"> & {\n  asChild?: boolean;\n};\n\nexport const ImageCropReset = ({\n  asChild = false,\n  children,\n  onClick,\n  ...props\n}: ImageCropResetProps) => {\n  const { resetCrop } = useImageCrop();\n\n  const handleClick = (e: MouseEvent<HTMLButtonElement>) => {\n    resetCrop();\n    onClick?.(e);\n  };\n\n  if (asChild) {\n    return (\n      <Slot.Root onClick={handleClick} {...props}>\n        {children}\n      </Slot.Root>\n    );\n  }\n\n  return (\n    <Button onClick={handleClick} size=\"icon\" variant=\"ghost\" {...props}>\n      {children ?? <RotateCcwIcon className=\"size-4\" />}\n    </Button>\n  );\n};\n\n// Keep the original Cropper component for backward compatibility\nexport type CropperProps = Omit<ReactCropProps, \"onChange\"> & {\n  file: File;\n  maxImageSize?: number;\n  onCrop?: (croppedImage: string) => void;\n  onChange?: ReactCropProps[\"onChange\"];\n};\n\nexport const Cropper = ({\n  onChange,\n  onComplete,\n  onCrop,\n  style,\n  className,\n  file,\n  maxImageSize,\n  ...props\n}: CropperProps) => (\n  <ImageCrop\n    file={file}\n    maxImageSize={maxImageSize}\n    onChange={onChange}\n    onComplete={onComplete}\n    onCrop={onCrop}\n    {...props}\n  >\n    <ImageCropContent className={className} style={style} />\n  </ImageCrop>\n);\n","target":"components/kibo-ui/image-crop/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"image-zoom","type":"registry:ui","title":"image-zoom","description":"Image zoom is a component that allows you to zoom in on an image.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["react-medium-image-zoom"],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport Zoom, {\n  type ControlledProps,\n  type UncontrolledProps,\n} from \"react-medium-image-zoom\";\nimport { cn } from \"@/lib/utils\";\n\nexport type ImageZoomProps = UncontrolledProps & {\n  isZoomed?: ControlledProps[\"isZoomed\"];\n  onZoomChange?: ControlledProps[\"onZoomChange\"];\n  className?: string;\n  backdropClassName?: string;\n};\n\nexport const ImageZoom = ({\n  className,\n  backdropClassName,\n  ...props\n}: ImageZoomProps) => (\n  <div\n    className={cn(\n      \"relative\",\n      \"[&_[data-rmiz-ghost]]:pointer-events-none [&_[data-rmiz-ghost]]:absolute\",\n      \"[&_[data-rmiz-btn-zoom]]:m-0 [&_[data-rmiz-btn-zoom]]:size-10 [&_[data-rmiz-btn-zoom]]:touch-manipulation [&_[data-rmiz-btn-zoom]]:appearance-none [&_[data-rmiz-btn-zoom]]:rounded-[50%] [&_[data-rmiz-btn-zoom]]:border-none [&_[data-rmiz-btn-zoom]]:bg-foreground/70 [&_[data-rmiz-btn-zoom]]:p-2 [&_[data-rmiz-btn-zoom]]:text-background [&_[data-rmiz-btn-zoom]]:outline-offset-2\",\n      \"[&_[data-rmiz-btn-unzoom]]:m-0 [&_[data-rmiz-btn-unzoom]]:size-10 [&_[data-rmiz-btn-unzoom]]:touch-manipulation [&_[data-rmiz-btn-unzoom]]:appearance-none [&_[data-rmiz-btn-unzoom]]:rounded-[50%] [&_[data-rmiz-btn-unzoom]]:border-none [&_[data-rmiz-btn-unzoom]]:bg-foreground/70 [&_[data-rmiz-btn-unzoom]]:p-2 [&_[data-rmiz-btn-unzoom]]:text-background [&_[data-rmiz-btn-unzoom]]:outline-offset-2\",\n      \"[&_[data-rmiz-btn-zoom]:not(:focus):not(:active)]:pointer-events-none [&_[data-rmiz-btn-zoom]:not(:focus):not(:active)]:absolute [&_[data-rmiz-btn-zoom]:not(:focus):not(:active)]:size-px [&_[data-rmiz-btn-zoom]:not(:focus):not(:active)]:overflow-hidden [&_[data-rmiz-btn-zoom]:not(:focus):not(:active)]:whitespace-nowrap [&_[data-rmiz-btn-zoom]:not(:focus):not(:active)]:[clip-path:inset(50%)] [&_[data-rmiz-btn-zoom]:not(:focus):not(:active)]:[clip:rect(0_0_0_0)]\",\n      \"[&_[data-rmiz-btn-zoom]]:absolute [&_[data-rmiz-btn-zoom]]:top-2.5 [&_[data-rmiz-btn-zoom]]:right-2.5 [&_[data-rmiz-btn-zoom]]:bottom-auto [&_[data-rmiz-btn-zoom]]:left-auto [&_[data-rmiz-btn-zoom]]:cursor-zoom-in\",\n      \"[&_[data-rmiz-btn-unzoom]]:absolute [&_[data-rmiz-btn-unzoom]]:top-5 [&_[data-rmiz-btn-unzoom]]:right-5 [&_[data-rmiz-btn-unzoom]]:bottom-auto [&_[data-rmiz-btn-unzoom]]:left-auto [&_[data-rmiz-btn-unzoom]]:z-[1] [&_[data-rmiz-btn-unzoom]]:cursor-zoom-out\",\n      '[&_[data-rmiz-content=\"found\"]_img]:cursor-zoom-in',\n      '[&_[data-rmiz-content=\"found\"]_svg]:cursor-zoom-in',\n      '[&_[data-rmiz-content=\"found\"]_[role=\"img\"]]:cursor-zoom-in',\n      '[&_[data-rmiz-content=\"found\"]_[data-zoom]]:cursor-zoom-in',\n      className\n    )}\n  >\n    <Zoom\n      classDialog={cn(\n        \"[&::backdrop]:hidden\",\n        \"[&[open]]:fixed [&[open]]:m-0 [&[open]]:h-dvh [&[open]]:max-h-none [&[open]]:w-dvw [&[open]]:max-w-none [&[open]]:overflow-hidden [&[open]]:border-0 [&[open]]:bg-transparent [&[open]]:p-0\",\n        \"[&_[data-rmiz-modal-overlay]]:absolute [&_[data-rmiz-modal-overlay]]:inset-0 [&_[data-rmiz-modal-overlay]]:transition-all\",\n        '[&_[data-rmiz-modal-overlay=\"hidden\"]]:bg-transparent',\n        '[&_[data-rmiz-modal-overlay=\"visible\"]]:bg-background/80 [&_[data-rmiz-modal-overlay=\"visible\"]]:backdrop-blur-md',\n        \"[&_[data-rmiz-modal-content]]:relative [&_[data-rmiz-modal-content]]:size-full\",\n        \"[&_[data-rmiz-modal-img]]:absolute [&_[data-rmiz-modal-img]]:origin-top-left [&_[data-rmiz-modal-img]]:cursor-zoom-out [&_[data-rmiz-modal-img]]:transition-transform\",\n        \"motion-reduce:[&_[data-rmiz-modal-img]]:transition-none motion-reduce:[&_[data-rmiz-modal-overlay]]:transition-none\",\n        backdropClassName\n      )}\n      {...props}\n    />\n  </div>\n);\n","target":"components/kibo-ui/image-zoom/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"kanban","type":"registry:ui","title":"kanban","description":"A kanban board is a visual tool that helps you manage and visualize your work. It is a board with columns, and each column represents a status, e.g. \"Backlog\", \"In Progress\", \"Done\".","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@dnd-kit/core","@dnd-kit/sortable","@dnd-kit/utilities","tunnel-rat"],"devDependencies":[],"registryDependencies":["card","scroll-area"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport type {\n  Announcements,\n  DndContextProps,\n  DragEndEvent,\n  DragOverEvent,\n  DragStartEvent,\n} from \"@dnd-kit/core\";\nimport {\n  closestCenter,\n  DndContext,\n  DragOverlay,\n  KeyboardSensor,\n  MouseSensor,\n  TouchSensor,\n  useDroppable,\n  useSensor,\n  useSensors,\n} from \"@dnd-kit/core\";\nimport { arrayMove, SortableContext, useSortable } from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport {\n  createContext,\n  type HTMLAttributes,\n  type ReactNode,\n  useContext,\n  useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport tunnel from \"tunnel-rat\";\nimport { Card } from \"@/components/ui/card\";\nimport { ScrollArea, ScrollBar } from \"@/components/ui/scroll-area\";\nimport { cn } from \"@/lib/utils\";\n\nconst t = tunnel();\n\nexport type { DragEndEvent } from \"@dnd-kit/core\";\n\ntype KanbanItemProps = {\n  id: string;\n  name: string;\n  column: string;\n} & Record<string, unknown>;\n\ntype KanbanColumnProps = {\n  id: string;\n  name: string;\n} & Record<string, unknown>;\n\ntype KanbanContextProps<\n  T extends KanbanItemProps = KanbanItemProps,\n  C extends KanbanColumnProps = KanbanColumnProps,\n> = {\n  columns: C[];\n  data: T[];\n  activeCardId: string | null;\n};\n\nconst KanbanContext = createContext<KanbanContextProps>({\n  columns: [],\n  data: [],\n  activeCardId: null,\n});\n\nexport type KanbanBoardProps = {\n  id: string;\n  children: ReactNode;\n  className?: string;\n};\n\nexport const KanbanBoard = ({ id, children, className }: KanbanBoardProps) => {\n  const { isOver, setNodeRef } = useDroppable({\n    id,\n  });\n\n  return (\n    <div\n      className={cn(\n        \"flex size-full min-h-40 flex-col divide-y overflow-hidden rounded-md border bg-secondary text-xs shadow-sm ring-2 transition-all\",\n        isOver ? \"ring-primary\" : \"ring-transparent\",\n        className\n      )}\n      ref={setNodeRef}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport type KanbanCardProps<T extends KanbanItemProps = KanbanItemProps> = T & {\n  children?: ReactNode;\n  className?: string;\n};\n\nexport const KanbanCard = <T extends KanbanItemProps = KanbanItemProps>({\n  id,\n  name,\n  children,\n  className,\n}: KanbanCardProps<T>) => {\n  const {\n    attributes,\n    listeners,\n    setNodeRef,\n    transition,\n    transform,\n    isDragging,\n  } = useSortable({\n    id,\n  });\n  const { activeCardId } = useContext(KanbanContext) as KanbanContextProps;\n\n  const style = {\n    transition,\n    transform: CSS.Transform.toString(transform),\n  };\n\n  return (\n    <>\n      <div style={style} {...listeners} {...attributes} ref={setNodeRef}>\n        <Card\n          className={cn(\n            \"cursor-grab gap-4 rounded-md p-3 shadow-sm\",\n            isDragging && \"pointer-events-none cursor-grabbing opacity-30\",\n            className\n          )}\n        >\n          {children ?? <p className=\"m-0 font-medium text-sm\">{name}</p>}\n        </Card>\n      </div>\n      {activeCardId === id && (\n        <t.In>\n          <Card\n            className={cn(\n              \"cursor-grab gap-4 rounded-md p-3 shadow-sm ring-2 ring-primary\",\n              isDragging && \"cursor-grabbing\",\n              className\n            )}\n          >\n            {children ?? <p className=\"m-0 font-medium text-sm\">{name}</p>}\n          </Card>\n        </t.In>\n      )}\n    </>\n  );\n};\n\nexport type KanbanCardsProps<T extends KanbanItemProps = KanbanItemProps> =\n  Omit<HTMLAttributes<HTMLDivElement>, \"children\" | \"id\"> & {\n    children: (item: T) => ReactNode;\n    id: string;\n  };\n\nexport const KanbanCards = <T extends KanbanItemProps = KanbanItemProps>({\n  children,\n  className,\n  ...props\n}: KanbanCardsProps<T>) => {\n  const { data } = useContext(KanbanContext) as KanbanContextProps<T>;\n  const filteredData = data.filter((item) => item.column === props.id);\n  const items = filteredData.map((item) => item.id);\n\n  return (\n    <ScrollArea className=\"overflow-hidden\">\n      <SortableContext items={items}>\n        <div\n          className={cn(\"flex flex-grow flex-col gap-2 p-2\", className)}\n          {...props}\n        >\n          {filteredData.map(children)}\n        </div>\n      </SortableContext>\n      <ScrollBar orientation=\"vertical\" />\n    </ScrollArea>\n  );\n};\n\nexport type KanbanHeaderProps = HTMLAttributes<HTMLDivElement>;\n\nexport const KanbanHeader = ({ className, ...props }: KanbanHeaderProps) => (\n  <div className={cn(\"m-0 p-2 font-semibold text-sm\", className)} {...props} />\n);\n\nexport type KanbanProviderProps<\n  T extends KanbanItemProps = KanbanItemProps,\n  C extends KanbanColumnProps = KanbanColumnProps,\n> = Omit<DndContextProps, \"children\"> & {\n  children: (column: C) => ReactNode;\n  className?: string;\n  columns: C[];\n  data: T[];\n  onDataChange?: (data: T[]) => void;\n  onDragStart?: (event: DragStartEvent) => void;\n  onDragEnd?: (event: DragEndEvent) => void;\n  onDragOver?: (event: DragOverEvent) => void;\n};\n\nexport const KanbanProvider = <\n  T extends KanbanItemProps = KanbanItemProps,\n  C extends KanbanColumnProps = KanbanColumnProps,\n>({\n  children,\n  onDragStart,\n  onDragEnd,\n  onDragOver,\n  className,\n  columns,\n  data,\n  onDataChange,\n  ...props\n}: KanbanProviderProps<T, C>) => {\n  const [activeCardId, setActiveCardId] = useState<string | null>(null);\n\n  const sensors = useSensors(\n    useSensor(MouseSensor),\n    useSensor(TouchSensor),\n    useSensor(KeyboardSensor)\n  );\n\n  const handleDragStart = (event: DragStartEvent) => {\n    const card = data.find((item) => item.id === event.active.id);\n    if (card) {\n      setActiveCardId(event.active.id as string);\n    }\n    onDragStart?.(event);\n  };\n\n  const handleDragOver = (event: DragOverEvent) => {\n    const { active, over } = event;\n\n    if (!over) {\n      return;\n    }\n\n    const activeItem = data.find((item) => item.id === active.id);\n    const overItem = data.find((item) => item.id === over.id);\n\n    if (!activeItem) {\n      return;\n    }\n\n    const activeColumn = activeItem.column;\n    const overColumn =\n      overItem?.column ||\n      columns.find((col) => col.id === over.id)?.id ||\n      columns[0]?.id;\n\n    if (activeColumn !== overColumn) {\n      let newData = [...data];\n      const activeIndex = newData.findIndex((item) => item.id === active.id);\n      const overIndex = newData.findIndex((item) => item.id === over.id);\n\n      newData[activeIndex].column = overColumn;\n      newData = arrayMove(newData, activeIndex, overIndex);\n\n      onDataChange?.(newData);\n    }\n\n    onDragOver?.(event);\n  };\n\n  const handleDragEnd = (event: DragEndEvent) => {\n    setActiveCardId(null);\n\n    onDragEnd?.(event);\n\n    const { active, over } = event;\n\n    if (!over || active.id === over.id) {\n      return;\n    }\n\n    let newData = [...data];\n\n    const oldIndex = newData.findIndex((item) => item.id === active.id);\n    const newIndex = newData.findIndex((item) => item.id === over.id);\n\n    newData = arrayMove(newData, oldIndex, newIndex);\n\n    onDataChange?.(newData);\n  };\n\n  const announcements: Announcements = {\n    onDragStart({ active }) {\n      const { name, column } = data.find((item) => item.id === active.id) ?? {};\n\n      return `Picked up the card \"${name}\" from the \"${column}\" column`;\n    },\n    onDragOver({ active, over }) {\n      const { name } = data.find((item) => item.id === active.id) ?? {};\n      const newColumn = columns.find((column) => column.id === over?.id)?.name;\n\n      return `Dragged the card \"${name}\" over the \"${newColumn}\" column`;\n    },\n    onDragEnd({ active, over }) {\n      const { name } = data.find((item) => item.id === active.id) ?? {};\n      const newColumn = columns.find((column) => column.id === over?.id)?.name;\n\n      return `Dropped the card \"${name}\" into the \"${newColumn}\" column`;\n    },\n    onDragCancel({ active }) {\n      const { name } = data.find((item) => item.id === active.id) ?? {};\n\n      return `Cancelled dragging the card \"${name}\"`;\n    },\n  };\n\n  return (\n    <KanbanContext.Provider value={{ columns, data, activeCardId }}>\n      <DndContext\n        accessibility={{ announcements }}\n        collisionDetection={closestCenter}\n        onDragEnd={handleDragEnd}\n        onDragOver={handleDragOver}\n        onDragStart={handleDragStart}\n        sensors={sensors}\n        {...props}\n      >\n        <div\n          className={cn(\n            \"grid size-full auto-cols-fr grid-flow-col gap-4\",\n            className\n          )}\n        >\n          {columns.map((column) => children(column))}\n        </div>\n        {typeof window !== \"undefined\" &&\n          createPortal(\n            <DragOverlay>\n              <t.Out />\n            </DragOverlay>,\n            document.body\n          )}\n      </DndContext>\n    </KanbanContext.Provider>\n  );\n};\n","target":"components/kibo-ui/kanban/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"list","type":"registry:ui","title":"list","description":"List views are a great way to show a list of tasks grouped by status and ranked by priority.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@dnd-kit/core","@dnd-kit/modifiers"],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport {\n  DndContext,\n  type DragEndEvent,\n  rectIntersection,\n  useDraggable,\n  useDroppable,\n} from \"@dnd-kit/core\";\nimport { restrictToVerticalAxis } from \"@dnd-kit/modifiers\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type { DragEndEvent } from \"@dnd-kit/core\";\n\ntype Status = {\n  id: string;\n  name: string;\n  color: string;\n};\n\ntype Feature = {\n  id: string;\n  name: string;\n  startAt: Date;\n  endAt: Date;\n  status: Status;\n};\n\nexport type ListItemsProps = {\n  children: ReactNode;\n  className?: string;\n};\n\nexport const ListItems = ({ children, className }: ListItemsProps) => (\n  <div className={cn(\"flex flex-1 flex-col gap-2 p-3\", className)}>\n    {children}\n  </div>\n);\n\nexport type ListHeaderProps =\n  | {\n      children: ReactNode;\n    }\n  | {\n      name: Status[\"name\"];\n      color: Status[\"color\"];\n      className?: string;\n    };\n\nexport const ListHeader = (props: ListHeaderProps) =>\n  \"children\" in props ? (\n    props.children\n  ) : (\n    <div\n      className={cn(\n        \"flex shrink-0 items-center gap-2 bg-foreground/5 p-3\",\n        props.className\n      )}\n    >\n      <div\n        className=\"h-2 w-2 rounded-full\"\n        style={{ backgroundColor: props.color }}\n      />\n      <p className=\"m-0 font-semibold text-sm\">{props.name}</p>\n    </div>\n  );\n\nexport type ListGroupProps = {\n  id: Status[\"id\"];\n  children: ReactNode;\n  className?: string;\n};\n\nexport const ListGroup = ({ id, children, className }: ListGroupProps) => {\n  const { setNodeRef, isOver } = useDroppable({ id });\n\n  return (\n    <div\n      className={cn(\n        \"bg-secondary transition-colors\",\n        isOver && \"bg-foreground/10\",\n        className\n      )}\n      ref={setNodeRef}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport type ListItemProps = Pick<Feature, \"id\" | \"name\"> & {\n  readonly index: number;\n  readonly parent: string;\n  readonly children?: ReactNode;\n  readonly className?: string;\n};\n\nexport const ListItem = ({\n  id,\n  name,\n  index,\n  parent,\n  children,\n  className,\n}: ListItemProps) => {\n  const { attributes, listeners, setNodeRef, transform, isDragging } =\n    useDraggable({\n      id,\n      data: { index, parent },\n    });\n\n  return (\n    <div\n      className={cn(\n        \"flex cursor-grab items-center gap-2 rounded-md border bg-background p-2 shadow-sm\",\n        isDragging && \"cursor-grabbing\",\n        className\n      )}\n      style={{\n        transform: transform\n          ? `translateX(${transform.x}px) translateY(${transform.y}px)`\n          : \"none\",\n      }}\n      {...listeners}\n      {...attributes}\n      ref={setNodeRef}\n    >\n      {children ?? <p className=\"m-0 font-medium text-sm\">{name}</p>}\n    </div>\n  );\n};\n\nexport type ListProviderProps = {\n  children: ReactNode;\n  onDragEnd: (event: DragEndEvent) => void;\n  className?: string;\n};\n\nexport const ListProvider = ({\n  children,\n  onDragEnd,\n  className,\n}: ListProviderProps) => (\n  <DndContext\n    collisionDetection={rectIntersection}\n    modifiers={[restrictToVerticalAxis]}\n    onDragEnd={onDragEnd}\n  >\n    <div className={cn(\"flex size-full flex-col\", className)}>{children}</div>\n  </DndContext>\n);\n","target":"components/kibo-ui/list/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"marquee","type":"registry:ui","title":"marquee","description":"Marquees are a great way to show a list of items in a horizontal scrolling motion.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["react-fast-marquee"],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport type { HTMLAttributes } from \"react\";\nimport type { MarqueeProps as FastMarqueeProps } from \"react-fast-marquee\";\nimport FastMarquee from \"react-fast-marquee\";\nimport { cn } from \"@/lib/utils\";\n\nexport type MarqueeProps = HTMLAttributes<HTMLDivElement>;\n\nexport const Marquee = ({ className, ...props }: MarqueeProps) => (\n  <div\n    className={cn(\"relative w-full overflow-hidden\", className)}\n    {...props}\n  />\n);\n\nexport type MarqueeContentProps = FastMarqueeProps;\n\nexport const MarqueeContent = ({\n  loop = 0,\n  autoFill = true,\n  pauseOnHover = true,\n  ...props\n}: MarqueeContentProps) => (\n  <FastMarquee\n    autoFill={autoFill}\n    loop={loop}\n    pauseOnHover={pauseOnHover}\n    {...props}\n  />\n);\n\nexport type MarqueeFadeProps = HTMLAttributes<HTMLDivElement> & {\n  side: \"left\" | \"right\";\n};\n\nexport const MarqueeFade = ({\n  className,\n  side,\n  ...props\n}: MarqueeFadeProps) => (\n  <div\n    className={cn(\n      \"absolute top-0 bottom-0 z-10 h-full w-24 from-background to-transparent\",\n      side === \"left\" ? \"left-0 bg-gradient-to-r\" : \"right-0 bg-gradient-to-l\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type MarqueeItemProps = HTMLAttributes<HTMLDivElement>;\n\nexport const MarqueeItem = ({ className, ...props }: MarqueeItemProps) => (\n  <div\n    className={cn(\"mx-2 flex-shrink-0 object-contain\", className)}\n    {...props}\n  />\n);\n","target":"components/kibo-ui/marquee/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"mini-calendar","type":"registry:ui","title":"mini-calendar","description":"A composable mini calendar component for picking dates close to today.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@radix-ui/react-use-controllable-state","date-fns","lucide-react","radix-ui"],"devDependencies":[],"registryDependencies":["button"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport { addDays, format, isSameDay, isToday } from \"date-fns\";\nimport { ChevronLeftIcon, ChevronRightIcon } from \"lucide-react\";\nimport { Slot } from \"radix-ui\";\nimport {\n  type ButtonHTMLAttributes,\n  type ComponentProps,\n  createContext,\n  type HTMLAttributes,\n  type MouseEventHandler,\n  type ReactNode,\n  useContext,\n} from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\n// Context for sharing state between components\ntype MiniCalendarContextType = {\n  selectedDate: Date | null | undefined;\n  onDateSelect: (date: Date) => void;\n  startDate: Date;\n  onNavigate: (direction: \"prev\" | \"next\") => void;\n  days: number;\n};\n\nconst MiniCalendarContext = createContext<MiniCalendarContextType | null>(null);\n\nconst useMiniCalendar = () => {\n  const context = useContext(MiniCalendarContext);\n\n  if (!context) {\n    throw new Error(\"MiniCalendar components must be used within MiniCalendar\");\n  }\n\n  return context;\n};\n\n// Helper function to get array of consecutive dates\nconst getDays = (startDate: Date, count: number): Date[] => {\n  const days: Date[] = [];\n  for (let i = 0; i < count; i++) {\n    days.push(addDays(startDate, i));\n  }\n  return days;\n};\n\n// Helper function to format date\nconst formatDate = (date: Date) => {\n  const month = format(date, \"MMM\");\n  const day = format(date, \"d\");\n\n  return { month, day };\n};\n\nexport type MiniCalendarProps = HTMLAttributes<HTMLDivElement> & {\n  value?: Date;\n  defaultValue?: Date;\n  onValueChange?: (date: Date | undefined) => void;\n  startDate?: Date;\n  defaultStartDate?: Date;\n  onStartDateChange?: (date: Date | undefined) => void;\n  days?: number;\n};\n\nexport const MiniCalendar = ({\n  value,\n  defaultValue,\n  onValueChange,\n  startDate,\n  defaultStartDate = new Date(),\n  onStartDateChange,\n  days = 5,\n  className,\n  children,\n  ...props\n}: MiniCalendarProps) => {\n  const [selectedDate, setSelectedDate] = useControllableState<\n    Date | undefined\n  >({\n    prop: value,\n    defaultProp: defaultValue,\n    onChange: onValueChange,\n  });\n\n  const [currentStartDate, setCurrentStartDate] = useControllableState({\n    prop: startDate,\n    defaultProp: defaultStartDate,\n    onChange: onStartDateChange,\n  });\n\n  const handleDateSelect = (date: Date) => {\n    setSelectedDate(date);\n  };\n\n  const handleNavigate = (direction: \"prev\" | \"next\") => {\n    const newStartDate = addDays(\n      currentStartDate || new Date(),\n      direction === \"next\" ? days : -days\n    );\n    setCurrentStartDate(newStartDate);\n  };\n\n  const contextValue: MiniCalendarContextType = {\n    selectedDate: selectedDate || null,\n    onDateSelect: handleDateSelect,\n    startDate: currentStartDate || new Date(),\n    onNavigate: handleNavigate,\n    days,\n  };\n\n  return (\n    <MiniCalendarContext.Provider value={contextValue}>\n      <div\n        className={cn(\n          \"flex items-center gap-2 rounded-lg border bg-background p-2\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    </MiniCalendarContext.Provider>\n  );\n};\n\nexport type MiniCalendarNavigationProps =\n  ButtonHTMLAttributes<HTMLButtonElement> & {\n    direction: \"prev\" | \"next\";\n    asChild?: boolean;\n  };\n\nexport const MiniCalendarNavigation = ({\n  direction,\n  asChild = false,\n  children,\n  onClick,\n  ...props\n}: MiniCalendarNavigationProps) => {\n  const { onNavigate } = useMiniCalendar();\n  const Icon = direction === \"prev\" ? ChevronLeftIcon : ChevronRightIcon;\n\n  const handleClick: MouseEventHandler<HTMLButtonElement> = (event) => {\n    onNavigate(direction);\n    onClick?.(event);\n  };\n\n  if (asChild) {\n    return (\n      <Slot.Root onClick={handleClick} {...props}>\n        {children}\n      </Slot.Root>\n    );\n  }\n\n  return (\n    <Button\n      onClick={handleClick}\n      size={asChild ? undefined : \"icon\"}\n      type=\"button\"\n      variant={asChild ? undefined : \"ghost\"}\n      {...props}\n    >\n      {children ?? <Icon className=\"size-4\" />}\n    </Button>\n  );\n};\n\nexport type MiniCalendarDaysProps = Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> & {\n  children: (date: Date) => ReactNode;\n};\n\nexport const MiniCalendarDays = ({\n  className,\n  children,\n  ...props\n}: MiniCalendarDaysProps) => {\n  const { startDate, days: dayCount } = useMiniCalendar();\n  const days = getDays(startDate, dayCount);\n\n  return (\n    <div className={cn(\"flex items-center gap-1\", className)} {...props}>\n      {days.map((date) => children(date))}\n    </div>\n  );\n};\n\nexport type MiniCalendarDayProps = ComponentProps<typeof Button> & {\n  date: Date;\n};\n\nexport const MiniCalendarDay = ({\n  date,\n  className,\n  ...props\n}: MiniCalendarDayProps) => {\n  const { selectedDate, onDateSelect } = useMiniCalendar();\n  const { month, day } = formatDate(date);\n  const isSelected = selectedDate && isSameDay(date, selectedDate);\n  const isTodayDate = isToday(date);\n\n  return (\n    <Button\n      className={cn(\n        \"h-auto min-w-[3rem] flex-col gap-0 p-2 text-xs\",\n        isTodayDate && !isSelected && \"bg-accent\",\n        className\n      )}\n      onClick={() => onDateSelect(date)}\n      size=\"sm\"\n      type=\"button\"\n      variant={isSelected ? \"default\" : \"ghost\"}\n      {...props}\n    >\n      <span\n        className={cn(\n          \"font-medium text-[10px] text-muted-foreground\",\n          isSelected && \"text-primary-foreground/70\"\n        )}\n      >\n        {month}\n      </span>\n      <span className=\"font-semibold text-sm\">{day}</span>\n    </Button>\n  );\n};\n","target":"components/kibo-ui/mini-calendar/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"pill","type":"registry:ui","title":"pill","description":"A flexible badge component designed for a variety of use cases.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["lucide-react"],"devDependencies":[],"registryDependencies":["avatar","badge","button"],"files":[{"type":"registry:ui","path":"index.tsx","content":"import { ChevronDownIcon, ChevronUpIcon, MinusIcon } from \"lucide-react\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nexport type PillProps = ComponentProps<typeof Badge> & {\n  themed?: boolean;\n};\n\nexport const Pill = ({\n  variant = \"secondary\",\n  themed = false,\n  className,\n  ...props\n}: PillProps) => (\n  <Badge\n    className={cn(\"gap-2 rounded-full px-3 py-1.5 font-normal\", className)}\n    variant={variant}\n    {...props}\n  />\n);\n\nexport type PillAvatarProps = ComponentProps<typeof AvatarImage> & {\n  fallback?: string;\n};\n\nexport const PillAvatar = ({\n  fallback,\n  className,\n  ...props\n}: PillAvatarProps) => (\n  <Avatar className={cn(\"-ml-1 h-4 w-4\", className)}>\n    <AvatarImage {...props} />\n    <AvatarFallback>{fallback}</AvatarFallback>\n  </Avatar>\n);\n\nexport type PillButtonProps = ComponentProps<typeof Button>;\n\nexport const PillButton = ({ className, ...props }: PillButtonProps) => (\n  <Button\n    className={cn(\n      \"-my-2 -mr-2 size-6 rounded-full p-0.5 hover:bg-foreground/5\",\n      className\n    )}\n    size=\"icon\"\n    variant=\"ghost\"\n    {...props}\n  />\n);\n\nexport type PillStatusProps = {\n  children: ReactNode;\n  className?: string;\n};\n\nexport const PillStatus = ({\n  children,\n  className,\n  ...props\n}: PillStatusProps) => (\n  <div\n    className={cn(\n      \"flex items-center gap-2 border-r pr-2 font-medium\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n  </div>\n);\n\nexport type PillIndicatorProps = {\n  variant?: \"success\" | \"error\" | \"warning\" | \"info\";\n  pulse?: boolean;\n};\n\nexport const PillIndicator = ({\n  variant = \"success\",\n  pulse = false,\n}: PillIndicatorProps) => (\n  <span className=\"relative flex size-2\">\n    {pulse && (\n      <span\n        className={cn(\n          \"absolute inline-flex h-full w-full animate-ping rounded-full opacity-75\",\n          variant === \"success\" && \"bg-emerald-400\",\n          variant === \"error\" && \"bg-rose-400\",\n          variant === \"warning\" && \"bg-amber-400\",\n          variant === \"info\" && \"bg-sky-400\"\n        )}\n      />\n    )}\n    <span\n      className={cn(\n        \"relative inline-flex size-2 rounded-full\",\n        variant === \"success\" && \"bg-emerald-500\",\n        variant === \"error\" && \"bg-rose-500\",\n        variant === \"warning\" && \"bg-amber-500\",\n        variant === \"info\" && \"bg-sky-500\"\n      )}\n    />\n  </span>\n);\n\nexport type PillDeltaProps = {\n  className?: string;\n  delta: number;\n};\n\nexport const PillDelta = ({ className, delta }: PillDeltaProps) => {\n  if (!delta) {\n    return (\n      <MinusIcon className={cn(\"size-3 text-muted-foreground\", className)} />\n    );\n  }\n\n  if (delta > 0) {\n    return (\n      <ChevronUpIcon className={cn(\"size-3 text-emerald-500\", className)} />\n    );\n  }\n\n  return <ChevronDownIcon className={cn(\"size-3 text-rose-500\", className)} />;\n};\n\nexport type PillIconProps = {\n  icon: typeof ChevronUpIcon;\n  className?: string;\n};\n\nexport const PillIcon = ({\n  icon: Icon,\n  className,\n  ...props\n}: PillIconProps) => (\n  <Icon\n    className={cn(\"size-3 text-muted-foreground\", className)}\n    size={12}\n    {...props}\n  />\n);\n\nexport type PillAvatarGroupProps = {\n  children: ReactNode;\n  className?: string;\n};\n\nexport const PillAvatarGroup = ({\n  children,\n  className,\n  ...props\n}: PillAvatarGroupProps) => (\n  <div\n    className={cn(\n      \"-space-x-1 flex items-center\",\n      \"[&>*:not(:first-of-type)]:[mask-image:radial-gradient(circle_9px_at_-4px_50%,transparent_99%,white_100%)]\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n  </div>\n);\n","target":"components/kibo-ui/pill/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"qr-code","type":"registry:ui","title":"qr-code","description":"QR Code is a component that generates a QR code from a string.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["culori","qrcode"],"devDependencies":["@types/culori","@types/qrcode"],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { formatHex, oklch } from \"culori\";\nimport QR from \"qrcode\";\nimport { type HTMLAttributes, useEffect, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type QRCodeProps = HTMLAttributes<HTMLDivElement> & {\n  data: string;\n  foreground?: string;\n  background?: string;\n  robustness?: \"L\" | \"M\" | \"Q\" | \"H\";\n};\n\nconst oklchRegex = /oklch\\(([0-9.]+)\\s+([0-9.]+)\\s+([0-9.]+)\\)/;\n\nconst getOklch = (color: string, fallback: [number, number, number]) => {\n  const oklchMatch = color.match(oklchRegex);\n\n  if (!oklchMatch) {\n    return { l: fallback[0], c: fallback[1], h: fallback[2] };\n  }\n\n  return {\n    l: Number.parseFloat(oklchMatch[1]),\n    c: Number.parseFloat(oklchMatch[2]),\n    h: Number.parseFloat(oklchMatch[3]),\n  };\n};\n\nexport const QRCode = ({\n  data,\n  foreground,\n  background,\n  robustness = \"M\",\n  className,\n  ...props\n}: QRCodeProps) => {\n  const [svg, setSVG] = useState<string | null>(null);\n\n  useEffect(() => {\n    const generateQR = async () => {\n      try {\n        const styles = getComputedStyle(document.documentElement);\n        const foregroundColor =\n          foreground ?? styles.getPropertyValue(\"--foreground\");\n        const backgroundColor =\n          background ?? styles.getPropertyValue(\"--background\");\n\n        const foregroundOklch = getOklch(\n          foregroundColor,\n          [0.21, 0.006, 285.885]\n        );\n        const backgroundOklch = getOklch(backgroundColor, [0.985, 0, 0]);\n\n        const newSvg = await QR.toString(data, {\n          type: \"svg\",\n          color: {\n            dark: formatHex(oklch({ mode: \"oklch\", ...foregroundOklch })),\n            light: formatHex(oklch({ mode: \"oklch\", ...backgroundOklch })),\n          },\n          width: 200,\n          errorCorrectionLevel: robustness,\n          margin: 0,\n        });\n\n        setSVG(newSvg);\n      } catch (err) {\n        console.error(err);\n      }\n    };\n\n    generateQR();\n  }, [data, foreground, background, robustness]);\n\n  if (!svg) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\"size-full\", \"[&_svg]:size-full\", className)}\n      // biome-ignore lint/security/noDangerouslySetInnerHtml: \"Required for SVG\"\n      dangerouslySetInnerHTML={{ __html: svg }}\n      {...props}\n    />\n  );\n};\n","target":"components/kibo-ui/qr-code/index.tsx"},{"type":"registry:ui","path":"server.tsx","content":"import QR from \"qrcode\";\nimport type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type QRCodeProps = HTMLAttributes<HTMLDivElement> & {\n  data: string;\n  foreground: string;\n  background: string;\n  robustness?: \"L\" | \"M\" | \"Q\" | \"H\";\n};\n\nexport const QRCode = async ({\n  data,\n  foreground,\n  background,\n  robustness = \"M\",\n  className,\n  ...props\n}: QRCodeProps) => {\n  const svg = await QR.toString(data, {\n    type: \"svg\",\n    color: {\n      dark: foreground,\n      light: background,\n    },\n    width: 200,\n    errorCorrectionLevel: robustness,\n  });\n\n  if (!svg) {\n    throw new Error(\"Failed to generate QR code\");\n  }\n\n  return (\n    <div\n      className={cn(\"size-full\", \"[&_svg]:size-full\", className)}\n      // biome-ignore lint/security/noDangerouslySetInnerHtml: \"Required for SVG\"\n      dangerouslySetInnerHTML={{ __html: svg }}\n      {...props}\n    />\n  );\n};\n","target":"components/kibo-ui/qr-code/server.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"rating","type":"registry:ui","title":"rating","description":"A star rating component with keyboard navigation and hover effects.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@radix-ui/react-use-controllable-state","lucide-react"],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport { type LucideProps, StarIcon } from \"lucide-react\";\nimport type { KeyboardEvent, MouseEvent, ReactElement, ReactNode } from \"react\";\nimport {\n  Children,\n  cloneElement,\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ntype RatingContextValue = {\n  value: number;\n  readOnly: boolean;\n  hoverValue: number | null;\n  focusedStar: number | null;\n  handleValueChange: (\n    event: MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLButtonElement>,\n    value: number\n  ) => void;\n  handleKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => void;\n  setHoverValue: (value: number | null) => void;\n  setFocusedStar: (value: number | null) => void;\n};\n\nconst RatingContext = createContext<RatingContextValue | null>(null);\n\nconst useRating = () => {\n  const context = useContext(RatingContext);\n  if (!context) {\n    throw new Error(\"useRating must be used within a Rating component\");\n  }\n  return context;\n};\n\nexport type RatingButtonProps = LucideProps & {\n  index?: number;\n  icon?: ReactElement<LucideProps>;\n};\n\nexport const RatingButton = ({\n  index: providedIndex,\n  size = 20,\n  className,\n  icon = <StarIcon />,\n}: RatingButtonProps) => {\n  const {\n    value,\n    readOnly,\n    hoverValue,\n    focusedStar,\n    handleValueChange,\n    handleKeyDown,\n    setHoverValue,\n    setFocusedStar,\n  } = useRating();\n\n  const index = providedIndex ?? 0;\n  const isActive = index < (hoverValue ?? focusedStar ?? value ?? 0);\n  let tabIndex = -1;\n\n  if (!readOnly) {\n    tabIndex = value === index + 1 ? 0 : -1;\n  }\n\n  const handleClick = useCallback(\n    (event: MouseEvent<HTMLButtonElement>) => {\n      handleValueChange(event, index + 1);\n    },\n    [handleValueChange, index]\n  );\n\n  const handleMouseEnter = useCallback(() => {\n    if (!readOnly) {\n      setHoverValue(index + 1);\n    }\n  }, [readOnly, setHoverValue, index]);\n\n  const handleFocus = useCallback(() => {\n    setFocusedStar(index + 1);\n  }, [setFocusedStar, index]);\n\n  const handleBlur = useCallback(() => {\n    setFocusedStar(null);\n  }, [setFocusedStar]);\n\n  return (\n    <button\n      className={cn(\n        \"rounded-full focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n        \"p-0.5\",\n        readOnly && \"cursor-default\",\n        className\n      )}\n      disabled={readOnly}\n      onBlur={handleBlur}\n      onClick={handleClick}\n      onFocus={handleFocus}\n      onKeyDown={handleKeyDown}\n      onMouseEnter={handleMouseEnter}\n      tabIndex={tabIndex}\n      type=\"button\"\n    >\n      {cloneElement(icon, {\n        size,\n        className: cn(\n          \"transition-colors duration-200\",\n          isActive && \"fill-current\",\n          !readOnly && \"cursor-pointer\"\n        ),\n        \"aria-hidden\": \"true\",\n      })}\n    </button>\n  );\n};\n\nexport type RatingProps = {\n  defaultValue?: number;\n  value?: number;\n  onChange?: (\n    event: MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLButtonElement>,\n    value: number\n  ) => void;\n  onValueChange?: (value: number) => void;\n  readOnly?: boolean;\n  className?: string;\n  children?: ReactNode;\n};\n\nexport const Rating = ({\n  value: controlledValue,\n  onValueChange: controlledOnValueChange,\n  defaultValue = 0,\n  onChange,\n  readOnly = false,\n  className,\n  children,\n  ...props\n}: RatingProps) => {\n  const [hoverValue, setHoverValue] = useState<number | null>(null);\n  const [focusedStar, setFocusedStar] = useState<number | null>(null);\n  const containerRef = useRef<HTMLDivElement>(null);\n  const [value, onValueChange] = useControllableState({\n    defaultProp: defaultValue,\n    prop: controlledValue,\n    onChange: controlledOnValueChange,\n  });\n\n  const handleValueChange = useCallback(\n    (\n      event: MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLButtonElement>,\n      newValue: number\n    ) => {\n      if (!readOnly) {\n        onChange?.(event, newValue);\n        onValueChange?.(newValue);\n      }\n    },\n    [readOnly, onChange, onValueChange]\n  );\n\n  const handleKeyDown = useCallback(\n    (event: KeyboardEvent<HTMLButtonElement>) => {\n      if (readOnly) {\n        return;\n      }\n\n      const total = Children.count(children);\n      let newValue = focusedStar !== null ? focusedStar : (value ?? 0);\n\n      switch (event.key) {\n        case \"ArrowRight\":\n          if (event.shiftKey || event.metaKey) {\n            newValue = total;\n          } else {\n            newValue = Math.min(total, newValue + 1);\n          }\n          break;\n        case \"ArrowLeft\":\n          if (event.shiftKey || event.metaKey) {\n            newValue = 1;\n          } else {\n            newValue = Math.max(1, newValue - 1);\n          }\n          break;\n        default:\n          return;\n      }\n\n      event.preventDefault();\n      setFocusedStar(newValue);\n      handleValueChange(event, newValue);\n    },\n    [focusedStar, value, children, readOnly, handleValueChange]\n  );\n\n  useEffect(() => {\n    if (focusedStar !== null && containerRef.current) {\n      const buttons = containerRef.current.querySelectorAll(\"button\");\n      buttons[focusedStar - 1]?.focus();\n    }\n  }, [focusedStar]);\n\n  const contextValue: RatingContextValue = {\n    value: value ?? 0,\n    readOnly,\n    hoverValue,\n    focusedStar,\n    handleValueChange,\n    handleKeyDown,\n    setHoverValue,\n    setFocusedStar,\n  };\n\n  return (\n    <RatingContext.Provider value={contextValue}>\n      <div\n        aria-label=\"Rating\"\n        className={cn(\"inline-flex items-center gap-0.5\", className)}\n        onMouseLeave={() => setHoverValue(null)}\n        ref={containerRef}\n        role=\"radiogroup\"\n        {...props}\n      >\n        {Children.map(children, (child, index) => {\n          if (!child) {\n            return null;\n          }\n\n          return cloneElement(child as ReactElement<RatingButtonProps>, {\n            index,\n          });\n        })}\n      </div>\n    </RatingContext.Provider>\n  );\n};\n","target":"components/kibo-ui/rating/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"reel","type":"registry:ui","title":"reel","description":"A composable Reel component that looks like Instagram Stories - a full-height, 9:16 aspect ratio container with video playback and progress indicators.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@radix-ui/react-use-controllable-state","lucide-react","motion"],"devDependencies":[],"registryDependencies":["button","progress"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport {\n  ChevronLeft,\n  ChevronRight,\n  Pause,\n  Play,\n  Volume2,\n  VolumeX,\n} from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport type {\n  ComponentProps,\n  HTMLAttributes,\n  MouseEventHandler,\n  ReactNode,\n  VideoHTMLAttributes,\n} from \"react\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Progress } from \"@/components/ui/progress\";\nimport { cn } from \"@/lib/utils\";\n\n// Explicit type for reel items\nexport type ReelItem = {\n  id: string | number;\n  type: \"video\" | \"image\";\n  src: string;\n  duration: number; // Duration in seconds for both video and image\n  alt?: string;\n  title?: string;\n  description?: string;\n};\n\ntype ReelContextType = {\n  currentIndex: number;\n  setCurrentIndex: (index: number) => void;\n  isPlaying: boolean;\n  setIsPlaying: (playing: boolean) => void;\n  isMuted: boolean;\n  setIsMuted: (muted: boolean) => void;\n  progress: number;\n  setProgress: (progress: number) => void;\n  duration: number;\n  setDuration: (duration: number) => void;\n  data: ReelItem[];\n  currentItem: ReelItem;\n  isNavigating: boolean;\n  setIsNavigating: (navigating: boolean) => void;\n  isTransitioning: boolean;\n  setIsTransitioning: (transitioning: boolean) => void;\n};\n\nconst ReelContext = createContext<ReelContextType | undefined>(undefined);\n\nconst useReelContext = () => {\n  const context = useContext(ReelContext);\n  if (!context) {\n    throw new Error(\"useReelContext must be used within a Reel\");\n  }\n  return context;\n};\n\nexport type ReelProps = HTMLAttributes<HTMLDivElement> & {\n  data: ReelItem[];\n  defaultIndex?: number;\n  index?: number;\n  onIndexChange?: (index: number) => void;\n  defaultPlaying?: boolean;\n  playing?: boolean;\n  onPlayingChange?: (playing: boolean) => void;\n  defaultMuted?: boolean;\n  muted?: boolean;\n  onMutedChange?: (muted: boolean) => void;\n  autoPlay?: boolean;\n};\n\nexport const Reel = ({\n  className,\n  data,\n  defaultIndex = 0,\n  index: controlledIndex,\n  onIndexChange: controlledOnIndexChange,\n  defaultPlaying,\n  playing: controlledPlaying,\n  onPlayingChange: controlledOnPlayingChange,\n  defaultMuted = true,\n  muted: controlledMuted,\n  onMutedChange: controlledOnMutedChange,\n  autoPlay = true,\n  ...props\n}: ReelProps) => {\n  const [currentIndex, setCurrentIndexState] = useControllableState({\n    defaultProp: defaultIndex,\n    prop: controlledIndex,\n    onChange: controlledOnIndexChange,\n  });\n\n  const [isPlaying, setIsPlaying] = useControllableState({\n    defaultProp: defaultPlaying ?? autoPlay,\n    prop: controlledPlaying,\n    onChange: controlledOnPlayingChange,\n  });\n\n  const [isMuted, setIsMuted] = useControllableState({\n    defaultProp: defaultMuted,\n    prop: controlledMuted,\n    onChange: controlledOnMutedChange,\n  });\n\n  const [progress, setProgress] = useState(0);\n  const [duration, setDuration] = useState(0);\n  const [isNavigating, setIsNavigating] = useState(false);\n  const [isTransitioning, setIsTransitioning] = useState(false);\n\n  const setCurrentIndex = useCallback(\n    (index: number) => {\n      setIsTransitioning(true);\n      setProgress(0); // Reset progress immediately to prevent showing 100% during transition\n      setCurrentIndexState(index);\n    },\n    [setCurrentIndexState]\n  );\n\n  const currentItem = data[currentIndex];\n\n  return (\n    <ReelContext.Provider\n      value={{\n        currentIndex,\n        setCurrentIndex,\n        isPlaying,\n        setIsPlaying,\n        isMuted,\n        setIsMuted,\n        progress,\n        setProgress,\n        duration,\n        setDuration,\n        data,\n        currentItem,\n        isNavigating,\n        setIsNavigating,\n        isTransitioning,\n        setIsTransitioning,\n      }}\n    >\n      <div\n        className={cn(\n          \"relative isolate h-full w-auto overflow-hidden bg-black\",\n          \"aspect-[9/16]\",\n          className\n        )}\n        {...props}\n      />\n    </ReelContext.Provider>\n  );\n};\n\nexport type ReelContentProps = Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> & {\n  children: (item: ReelItem, index: number) => ReactNode;\n};\n\nexport const ReelContent = ({\n  className,\n  children,\n  ...props\n}: ReelContentProps) => {\n  const { currentIndex, currentItem, setIsTransitioning } = useReelContext();\n\n  const renderContent = () => {\n    if (typeof children === \"function\") {\n      return children(currentItem, currentIndex);\n    }\n    const childrenArray = Array.isArray(children) ? children : [children];\n    return childrenArray[currentIndex];\n  };\n\n  return (\n    <div\n      className={cn(\"relative size-full\", className)}\n      data-reel-content\n      {...props}\n    >\n      <AnimatePresence mode=\"wait\">\n        <motion.div\n          animate={{ opacity: 1 }}\n          className=\"absolute inset-0\"\n          exit={{ opacity: 0 }}\n          initial={{ opacity: 0 }}\n          key={currentIndex}\n          onAnimationComplete={() => {\n            // Mark transition as complete when fade-in completes\n            setIsTransitioning(false);\n          }}\n          transition={{ duration: 0.3 }}\n        >\n          {renderContent()}\n        </motion.div>\n      </AnimatePresence>\n    </div>\n  );\n};\n\nexport type ReelItemProps = HTMLAttributes<HTMLDivElement>;\n\nexport const ReelItem = ({ className, ...props }: ReelItemProps) => (\n  <div\n    className={cn(\"relative size-full overflow-hidden\", className)}\n    data-reel-item\n    {...props}\n  />\n);\n\nexport type ReelVideoProps = VideoHTMLAttributes<HTMLVideoElement>;\n\nconst MS_TO_SECONDS = 1000;\nconst PERCENTAGE = 100;\n\nexport const ReelVideo = ({ className, ...props }: ReelVideoProps) => {\n  const videoRef = useRef<HTMLVideoElement>(null);\n  const {\n    isPlaying,\n    isMuted,\n    setDuration,\n    setProgress,\n    currentIndex,\n    setCurrentIndex,\n    data,\n    progress,\n    currentItem,\n    isTransitioning,\n  } = useReelContext();\n  const animationFrameRef = useRef<number | undefined>(undefined);\n  const startTimeRef = useRef<number | undefined>(undefined);\n  const pausedProgressRef = useRef<number>(0);\n  const duration = currentItem.duration;\n\n  // Set duration when component mounts or currentIndex changes\n  useEffect(() => {\n    setDuration(duration);\n    // Don't reset progress here anymore - it's handled in ReelContent after transition\n    if (!isTransitioning) {\n      pausedProgressRef.current = 0;\n    }\n  }, [duration, setDuration, isTransitioning]);\n\n  // Handle muting\n  useEffect(() => {\n    const video = videoRef.current;\n    if (!video) {\n      return;\n    }\n    video.muted = isMuted;\n  }, [isMuted]);\n\n  // Store progress when pausing\n  useEffect(() => {\n    if (!isPlaying) {\n      pausedProgressRef.current = progress;\n    }\n  }, [isPlaying, progress]);\n\n  // Handle play/pause with duration-based progress\n  useEffect(() => {\n    const video = videoRef.current;\n    if (!video) {\n      return;\n    }\n\n    if (isPlaying && !isTransitioning) {\n      video.play().catch(() => {\n        // Ignore autoplay errors\n      });\n\n      // Start progress animation only when not transitioning\n      const elapsedTime = (pausedProgressRef.current * duration) / PERCENTAGE;\n      startTimeRef.current = performance.now() - elapsedTime * MS_TO_SECONDS;\n\n      const updateProgress = (currentTime: number) => {\n        const elapsed =\n          (currentTime - (startTimeRef.current || 0)) / MS_TO_SECONDS;\n        const newProgress = (elapsed / duration) * PERCENTAGE;\n\n        if (newProgress >= PERCENTAGE) {\n          const totalItems = data?.length || 0;\n          if (currentIndex < totalItems - 1) {\n            setCurrentIndex(currentIndex + 1);\n          } else {\n            setCurrentIndex(0);\n          }\n        } else {\n          setProgress(newProgress);\n          animationFrameRef.current = requestAnimationFrame(updateProgress);\n        }\n      };\n\n      animationFrameRef.current = requestAnimationFrame(updateProgress);\n    } else if (!isTransitioning) {\n      video.pause();\n    }\n\n    return () => {\n      if (animationFrameRef.current) {\n        cancelAnimationFrame(animationFrameRef.current);\n      }\n    };\n  }, [\n    isPlaying,\n    duration,\n    currentIndex,\n    setProgress,\n    setCurrentIndex,\n    data,\n    isTransitioning,\n  ]);\n\n  // biome-ignore lint/correctness/useExhaustiveDependencies: Reset video when index changes\n  useEffect(() => {\n    const video = videoRef.current;\n    if (video) {\n      video.currentTime = 0;\n    }\n  }, [currentIndex]);\n\n  return (\n    <video\n      className={cn(\"absolute inset-0 size-full object-cover\", className)}\n      loop\n      muted={isMuted}\n      playsInline\n      ref={videoRef}\n      {...props}\n    />\n  );\n};\n\nexport type ReelImageProps = Omit<ComponentProps<\"img\">, \"alt\"> & {\n  alt: string;\n  duration?: number;\n  width?: number | string;\n  height?: number | string;\n};\n\nconst DEFAULT_IMAGE_DURATION = 5;\n\nexport const ReelImage = ({\n  className,\n  alt,\n  duration = DEFAULT_IMAGE_DURATION,\n  width,\n  height,\n  ...props\n}: ReelImageProps) => {\n  const {\n    isPlaying,\n    setDuration,\n    setProgress,\n    currentIndex,\n    setCurrentIndex,\n    data,\n    progress,\n    isTransitioning,\n  } = useReelContext();\n  const animationFrameRef = useRef<number | undefined>(undefined);\n  const startTimeRef = useRef<number | undefined>(undefined);\n  const pausedProgressRef = useRef<number>(0);\n\n  // biome-ignore lint/correctness/useExhaustiveDependencies: Reset progress when index changes\n  useEffect(() => {\n    setDuration(duration);\n    // Don't reset progress here anymore - it's handled in ReelContent after transition\n    if (!isTransitioning) {\n      pausedProgressRef.current = 0;\n    }\n  }, [currentIndex, duration, setDuration, isTransitioning]);\n\n  // Handle play/pause\n  useEffect(() => {\n    if (isPlaying && !isTransitioning) {\n      const elapsedTime = (pausedProgressRef.current * duration) / PERCENTAGE;\n      startTimeRef.current = performance.now() - elapsedTime * MS_TO_SECONDS;\n\n      const updateProgress = (currentTime: number) => {\n        const elapsed =\n          (currentTime - (startTimeRef.current || 0)) / MS_TO_SECONDS;\n        const newProgress = (elapsed / duration) * PERCENTAGE;\n\n        if (newProgress >= PERCENTAGE) {\n          const totalItems = data?.length || 0;\n\n          if (currentIndex < totalItems - 1) {\n            setCurrentIndex(currentIndex + 1);\n          } else {\n            setCurrentIndex(0);\n          }\n        } else {\n          setProgress(newProgress);\n          pausedProgressRef.current = newProgress;\n          animationFrameRef.current = requestAnimationFrame(updateProgress);\n        }\n      };\n\n      animationFrameRef.current = requestAnimationFrame(updateProgress);\n    } else if (!isTransitioning) {\n      pausedProgressRef.current = progress;\n    }\n\n    return () => {\n      if (animationFrameRef.current) {\n        cancelAnimationFrame(animationFrameRef.current);\n      }\n    };\n  }, [\n    isPlaying,\n    duration,\n    currentIndex,\n    setProgress,\n    setCurrentIndex,\n    data,\n    progress,\n    isTransitioning,\n  ]);\n\n  return (\n    // biome-ignore lint/performance/noImgElement: \"Reel is framework-agnostic\"\n    <img\n      alt={alt}\n      className={cn(\"absolute inset-0 size-full object-cover\", className)}\n      height={height}\n      width={width}\n      {...props}\n    />\n  );\n};\n\nexport type ReelProgressProps = HTMLAttributes<HTMLDivElement> & {\n  children?: (\n    item: ReelItem,\n    index: number,\n    isActive: boolean,\n    progress: number\n  ) => ReactNode;\n};\n\nexport const ReelProgress = ({\n  className,\n  children,\n  ...props\n}: ReelProgressProps) => {\n  const { progress, currentIndex, data } = useReelContext();\n  const FULL_PROGRESS = 100;\n\n  const calculateProgress = (index: number) => {\n    if (index < currentIndex) {\n      return FULL_PROGRESS;\n    }\n    if (index === currentIndex) {\n      return progress;\n    }\n\n    return 0;\n  };\n\n  if (typeof children === \"function\") {\n    return (\n      <div\n        className={cn(\n          \"absolute top-0 right-0 left-0 z-40 flex gap-1 p-2\",\n          className\n        )}\n        {...props}\n      >\n        {data.map((item, index) => (\n          <div className=\"relative flex-1\" key={`${item.id}-progress`}>\n            {children(\n              item,\n              index,\n              index === currentIndex,\n              calculateProgress(index)\n            )}\n          </div>\n        ))}\n      </div>\n    );\n  }\n\n  return (\n    <div\n      className={cn(\n        \"absolute top-0 right-0 left-0 z-40 flex gap-1 p-2\",\n        className\n      )}\n      {...props}\n    >\n      {data.map((item, index) => (\n        <Progress\n          className=\"h-0.5 flex-1 bg-white/30 [&>div]:bg-white [&>div]:transition-none\"\n          key={`${item.id}-progress`}\n          value={calculateProgress(index)}\n        />\n      ))}\n    </div>\n  );\n};\n\nexport type ReelControlsProps = HTMLAttributes<HTMLDivElement>;\n\nexport const ReelControls = ({ className, ...props }: ReelControlsProps) => (\n  <div\n    className={cn(\n      \"absolute right-0 bottom-0 left-0 z-20 flex items-center justify-between p-4\",\n      \"bg-gradient-to-t from-black/60 to-transparent\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type ReelPreviousButtonProps = ComponentProps<typeof Button>;\n\nexport const ReelPreviousButton = ({\n  className,\n  children,\n  ...props\n}: ReelPreviousButtonProps) => {\n  const { currentIndex, setCurrentIndex, setIsNavigating } = useReelContext();\n  const NAVIGATION_RESET_DELAY = 50;\n\n  const handlePrevious = () => {\n    if (currentIndex > 0) {\n      setIsNavigating(true);\n      setCurrentIndex(currentIndex - 1);\n      setTimeout(() => setIsNavigating(false), NAVIGATION_RESET_DELAY);\n    }\n  };\n\n  return (\n    <Button\n      aria-label=\"Previous\"\n      className={cn(\n        \"rounded-full text-white hover:bg-white/10 hover:text-white\",\n        className\n      )}\n      disabled={currentIndex === 0}\n      onClick={handlePrevious}\n      size=\"icon\"\n      type=\"button\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children || <ChevronLeft className=\"size-4\" />}\n    </Button>\n  );\n};\n\nexport type ReelNextButtonProps = ComponentProps<typeof Button>;\n\nexport const ReelNextButton = ({\n  className,\n  children,\n  ...props\n}: ReelNextButtonProps) => {\n  const { currentIndex, setCurrentIndex, data, setIsNavigating } =\n    useReelContext();\n  const totalItems = data?.length || 0;\n  const NAVIGATION_RESET_DELAY = 50;\n\n  const handleNext = () => {\n    if (currentIndex < totalItems - 1) {\n      setIsNavigating(true);\n      setCurrentIndex(currentIndex + 1);\n      setTimeout(() => setIsNavigating(false), NAVIGATION_RESET_DELAY);\n    }\n  };\n\n  return (\n    <Button\n      aria-label=\"Next\"\n      className={cn(\n        \"rounded-full text-white hover:bg-white/10 hover:text-white\",\n        className\n      )}\n      disabled={currentIndex === totalItems - 1}\n      onClick={handleNext}\n      size=\"icon\"\n      type=\"button\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children || <ChevronRight className=\"size-4\" />}\n    </Button>\n  );\n};\n\nexport type ReelPlayButtonProps = ComponentProps<typeof Button>;\n\nexport const ReelPlayButton = ({\n  className,\n  children,\n  ...props\n}: ReelPlayButtonProps) => {\n  const { isPlaying, setIsPlaying } = useReelContext();\n\n  return (\n    <Button\n      aria-label={isPlaying ? \"Pause\" : \"Play\"}\n      className={cn(\n        \"rounded-full text-white hover:bg-white/10 hover:text-white\",\n        className\n      )}\n      onClick={() => setIsPlaying(!isPlaying)}\n      size=\"icon\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children ||\n        (isPlaying ? (\n          <Pause className=\"size-4\" />\n        ) : (\n          <Play className=\"size-4\" />\n        ))}\n    </Button>\n  );\n};\n\nexport type ReelMuteButtonProps = ComponentProps<typeof Button>;\n\nexport const ReelMuteButton = ({\n  className,\n  children,\n  ...props\n}: ReelMuteButtonProps) => {\n  const { isMuted, setIsMuted } = useReelContext();\n\n  return (\n    <Button\n      aria-label={isMuted ? \"Unmute\" : \"Mute\"}\n      className={cn(\n        \"rounded-full text-white hover:bg-white/10 hover:text-white\",\n        className\n      )}\n      onClick={() => setIsMuted(!isMuted)}\n      size=\"icon\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children ||\n        (isMuted ? (\n          <VolumeX className=\"size-4\" />\n        ) : (\n          <Volume2 className=\"size-4\" />\n        ))}\n    </Button>\n  );\n};\n\nexport type ReelNavigationProps = HTMLAttributes<HTMLButtonElement>;\n\nexport const ReelNavigation = ({\n  className,\n  ...props\n}: ReelNavigationProps) => {\n  const { setCurrentIndex, currentIndex, data, setIsNavigating } =\n    useReelContext();\n  const totalItems = data?.length || 0;\n  const NAVIGATION_RESET_DELAY = 50;\n  const HALF_WIDTH_DIVISOR = 2;\n\n  const handleClick: MouseEventHandler<HTMLButtonElement> = (e) => {\n    const rect = e.currentTarget.getBoundingClientRect();\n    const x = e.clientX - rect.left;\n    const width = rect.width;\n\n    if (x < width / HALF_WIDTH_DIVISOR) {\n      if (currentIndex > 0) {\n        setIsNavigating(true);\n        setCurrentIndex(currentIndex - 1);\n        setTimeout(() => setIsNavigating(false), NAVIGATION_RESET_DELAY);\n      }\n    } else if (currentIndex < totalItems - 1) {\n      setIsNavigating(true);\n      setCurrentIndex(currentIndex + 1);\n      setTimeout(() => setIsNavigating(false), NAVIGATION_RESET_DELAY);\n    }\n  };\n\n  return (\n    <button\n      className={cn(\"absolute inset-0 z-10 flex\", className)}\n      onClick={handleClick}\n      type=\"button\"\n      {...props}\n    >\n      <div className=\"flex-1 cursor-pointer\" />\n      <div className=\"flex-1 cursor-pointer\" />\n    </button>\n  );\n};\n\nexport type ReelOverlayProps = HTMLAttributes<HTMLDivElement>;\n\nexport const ReelOverlay = ({ className, ...props }: ReelOverlayProps) => (\n  <div\n    className={cn(\"pointer-events-none absolute inset-0 z-30\", className)}\n    {...props}\n  />\n);\n\nexport type ReelHeaderProps = HTMLAttributes<HTMLDivElement>;\n\nexport const ReelHeader = ({ className, ...props }: ReelHeaderProps) => (\n  <div\n    className={cn(\n      \"absolute top-0 right-0 left-0 z-20 p-4 pt-6\",\n      \"bg-gradient-to-b from-black/60 to-transparent\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type ReelFooterProps = HTMLAttributes<HTMLDivElement>;\n\nexport const ReelFooter = ({ className, ...props }: ReelFooterProps) => (\n  <div\n    className={cn(\n      \"absolute right-0 bottom-0 left-0 z-20 p-4\",\n      \"bg-gradient-to-t from-black/60 to-transparent\",\n      className\n    )}\n    {...props}\n  />\n);\n","target":"components/kibo-ui/reel/index.tsx"},{"type":"registry:ui","path":"reel-controlled.tsx","content":"\"use client\";\n\nimport { useState } from \"react\";\nimport {\n  Reel,\n  ReelContent,\n  ReelControls,\n  ReelFooter,\n  ReelHeader,\n  ReelImage,\n  type ReelItem,\n  ReelNavigation,\n  ReelProgress,\n  ReelVideo,\n} from \"./index\";\n\n// Example data for the reel\nconst reelItems: ReelItem[] = [\n  {\n    id: \"video-1\",\n    type: \"video\",\n    src: \"/videos/sample-video-1.mp4\",\n    duration: 10, // 10 seconds\n    title: \"First Video\",\n    description: \"This is the first video in the reel\",\n  },\n  {\n    id: \"image-1\",\n    type: \"image\",\n    src: \"/images/sample-landscape.jpg\",\n    duration: 5, // 5 seconds\n    alt: \"Beautiful landscape\",\n    title: \"Nature Photo\",\n    description: \"A stunning landscape photograph\",\n  },\n  {\n    id: \"video-2\",\n    type: \"video\",\n    src: \"/videos/sample-video-2.mp4\",\n    duration: 15, // 15 seconds\n    title: \"Second Video\",\n    description: \"Another exciting video\",\n  },\n  {\n    id: \"image-2\",\n    type: \"image\",\n    src: \"/images/sample-cityscape.jpg\",\n    duration: 7, // 7 seconds\n    alt: \"City skyline\",\n    title: \"Urban Photography\",\n    description: \"Modern city architecture\",\n  },\n];\n\nexport function ReelControlledExample() {\n  // Controlled state for all reel properties\n  const [currentIndex, setCurrentIndex] = useState(0);\n  const [isPlaying, setIsPlaying] = useState(true);\n  const [isMuted, setIsMuted] = useState(true);\n\n  // External controls\n  const handleJumpToItem = (index: number) => {\n    setCurrentIndex(index);\n  };\n\n  const handlePlayPause = () => {\n    setIsPlaying(!isPlaying);\n  };\n\n  const handleToggleMute = () => {\n    setIsMuted(!isMuted);\n  };\n\n  const handleNext = () => {\n    if (currentIndex < reelItems.length - 1) {\n      setCurrentIndex(currentIndex + 1);\n    } else {\n      setCurrentIndex(0); // Loop back to start\n    }\n  };\n\n  const handlePrevious = () => {\n    if (currentIndex > 0) {\n      setCurrentIndex(currentIndex - 1);\n    } else {\n      setCurrentIndex(reelItems.length - 1); // Loop to end\n    }\n  };\n\n  const currentItem = reelItems[currentIndex];\n\n  return (\n    <div className=\"flex flex-col gap-6\">\n      {/* External Controls */}\n      <div className=\"flex flex-col gap-4 rounded-lg border p-4\">\n        <h3 className=\"font-semibold text-lg\">External Controls</h3>\n\n        <div className=\"flex gap-2\">\n          <button\n            className=\"rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600\"\n            onClick={handlePrevious}\n            type=\"button\"\n          >\n            Previous\n          </button>\n          <button\n            className=\"rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600\"\n            onClick={handlePlayPause}\n            type=\"button\"\n          >\n            {isPlaying ? \"Pause\" : \"Play\"}\n          </button>\n          <button\n            className=\"rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600\"\n            onClick={handleNext}\n            type=\"button\"\n          >\n            Next\n          </button>\n          <button\n            className=\"rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600\"\n            onClick={handleToggleMute}\n            type=\"button\"\n          >\n            {isMuted ? \"Unmute\" : \"Mute\"}\n          </button>\n        </div>\n\n        <div className=\"flex gap-2\">\n          {reelItems.map((item, index) => (\n            <button\n              className={`rounded px-3 py-1 ${\n                index === currentIndex\n                  ? \"bg-blue-500 text-white\"\n                  : \"bg-gray-200 hover:bg-gray-300\"\n              }`}\n              key={item.id}\n              onClick={() => handleJumpToItem(index)}\n              type=\"button\"\n            >\n              {index + 1}\n            </button>\n          ))}\n        </div>\n\n        <div className=\"text-gray-600 text-sm\">\n          <p>Current Index: {currentIndex}</p>\n          <p>Playing: {isPlaying ? \"Yes\" : \"No\"}</p>\n          <p>Muted: {isMuted ? \"Yes\" : \"No\"}</p>\n          <p>Current Item: {currentItem.title}</p>\n        </div>\n      </div>\n\n      {/* Controlled Reel Component */}\n      <div className=\"mx-auto w-full max-w-sm\">\n        <Reel\n          data={reelItems}\n          index={currentIndex}\n          muted={isMuted}\n          onIndexChange={setCurrentIndex}\n          onMutedChange={setIsMuted}\n          onPlayingChange={setIsPlaying}\n          playing={isPlaying}\n        >\n          <ReelProgress />\n\n          <ReelHeader>\n            <div className=\"text-white\">\n              <h2 className=\"font-bold text-xl\">Controlled Reel</h2>\n              <p className=\"text-sm opacity-80\">\n                Item {currentIndex + 1} of {reelItems.length}\n              </p>\n            </div>\n          </ReelHeader>\n\n          <ReelContent>\n            {(item) => (\n              <>\n                {item.type === \"video\" ? (\n                  <ReelVideo src={item.src} />\n                ) : (\n                  <ReelImage\n                    alt={item.alt || \"\"}\n                    duration={item.duration}\n                    src={item.src}\n                  />\n                )}\n              </>\n            )}\n          </ReelContent>\n\n          <ReelNavigation />\n\n          <ReelFooter>\n            <div className=\"text-white\">\n              <h3 className=\"font-semibold text-lg\">{currentItem.title}</h3>\n              {currentItem.description && (\n                <p className=\"mt-1 text-sm opacity-90\">\n                  {currentItem.description}\n                </p>\n              )}\n            </div>\n          </ReelFooter>\n\n          <ReelControls />\n        </Reel>\n      </div>\n\n      {/* Advanced Controlled Example with Custom UI */}\n      <div className=\"flex flex-col gap-4 rounded-lg border p-4\">\n        <h3 className=\"font-semibold text-lg\">Advanced Controlled Example</h3>\n        <p className=\"text-gray-600 text-sm\">\n          This example shows how you can build a completely custom UI while\n          controlling the Reel state from outside.\n        </p>\n\n        <div className=\"flex gap-4\">\n          <div className=\"flex-1\">\n            <Reel\n              className=\"aspect-[9/16]\"\n              data={reelItems}\n              index={currentIndex}\n              muted={isMuted}\n              onIndexChange={setCurrentIndex}\n              onMutedChange={setIsMuted}\n              onPlayingChange={setIsPlaying}\n              playing={isPlaying}\n            >\n              <ReelContent>\n                {(item) => (\n                  <>\n                    {item.type === \"video\" ? (\n                      <ReelVideo src={item.src} />\n                    ) : (\n                      <ReelImage\n                        alt={item.alt || \"\"}\n                        duration={item.duration}\n                        src={item.src}\n                      />\n                    )}\n                  </>\n                )}\n              </ReelContent>\n            </Reel>\n          </div>\n\n          <div className=\"flex flex-1 flex-col justify-center gap-4\">\n            <div>\n              <h4 className=\"font-semibold\">{currentItem.title}</h4>\n              <p className=\"text-gray-600 text-sm\">{currentItem.description}</p>\n            </div>\n\n            <div className=\"space-y-2\">\n              <div className=\"flex items-center gap-2\">\n                <label className=\"font-medium text-sm\" htmlFor=\"timeline\">\n                  Timeline:\n                </label>\n                <input\n                  className=\"flex-1\"\n                  id=\"timeline\"\n                  max={reelItems.length - 1}\n                  min={0}\n                  onChange={(e) => setCurrentIndex(Number(e.target.value))}\n                  type=\"range\"\n                  value={currentIndex}\n                />\n              </div>\n\n              <div className=\"flex items-center gap-2\">\n                <input\n                  checked={isPlaying}\n                  id=\"autoplay\"\n                  onChange={(e) => setIsPlaying(e.target.checked)}\n                  type=\"checkbox\"\n                />\n                <label className=\"text-sm\" htmlFor=\"autoplay\">\n                  Auto-play\n                </label>\n              </div>\n\n              <div className=\"flex items-center gap-2\">\n                <input\n                  checked={isMuted}\n                  id=\"muted\"\n                  onChange={(e) => setIsMuted(e.target.checked)}\n                  type=\"checkbox\"\n                />\n                <label className=\"text-sm\" htmlFor=\"muted\">\n                  Muted\n                </label>\n              </div>\n            </div>\n\n            <div className=\"grid grid-cols-2 gap-2\">\n              {reelItems.map((item, index) => (\n                <button\n                  className={`rounded p-2 text-xs ${\n                    index === currentIndex\n                      ? \"bg-blue-500 text-white\"\n                      : \"bg-gray-100 hover:bg-gray-200\"\n                  }`}\n                  key={item.id}\n                  onClick={() => setCurrentIndex(index)}\n                  type=\"button\"\n                >\n                  {item.title}\n                </button>\n              ))}\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\n// Example of using the controlled Reel in a parent component\nexport default function ControlledReelPage() {\n  return (\n    <div className=\"container mx-auto py-8\">\n      <h1 className=\"mb-8 text-center font-bold text-3xl\">\n        Controlled Reel Examples\n      </h1>\n      <ReelControlledExample />\n    </div>\n  );\n}\n","target":"components/kibo-ui/reel/reel-controlled.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"relative-time","type":"registry:ui","title":"relative-time","description":"A component that displays time in various timezones.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@radix-ui/react-use-controllable-state"],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport {\n  createContext,\n  type HTMLAttributes,\n  useContext,\n  useEffect,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst formatDate = (\n  date: Date,\n  timeZone: string,\n  options?: Intl.DateTimeFormatOptions\n) =>\n  new Intl.DateTimeFormat(\n    \"en-US\",\n    options ?? {\n      dateStyle: \"long\",\n      timeZone,\n    }\n  ).format(date);\n\nconst formatTime = (\n  date: Date,\n  timeZone: string,\n  options?: Intl.DateTimeFormatOptions\n) =>\n  new Intl.DateTimeFormat(\n    \"en-US\",\n    options ?? {\n      hour: \"2-digit\",\n      minute: \"2-digit\",\n      second: \"2-digit\",\n      timeZone,\n    }\n  ).format(date);\n\ntype RelativeTimeContextType = {\n  time: Date;\n  dateFormatOptions?: Intl.DateTimeFormatOptions;\n  timeFormatOptions?: Intl.DateTimeFormatOptions;\n};\n\nconst RelativeTimeContext = createContext<RelativeTimeContextType>({\n  time: new Date(),\n  dateFormatOptions: {\n    dateStyle: \"long\",\n  },\n  timeFormatOptions: {\n    hour: \"2-digit\",\n    minute: \"2-digit\",\n  },\n});\n\nexport type RelativeTimeProps = HTMLAttributes<HTMLDivElement> & {\n  time?: Date;\n  defaultTime?: Date;\n  onTimeChange?: (time: Date) => void;\n  dateFormatOptions?: Intl.DateTimeFormatOptions;\n  timeFormatOptions?: Intl.DateTimeFormatOptions;\n};\n\nexport const RelativeTime = ({\n  time: controlledTime,\n  defaultTime = new Date(),\n  onTimeChange,\n  dateFormatOptions,\n  timeFormatOptions,\n  className,\n  ...props\n}: RelativeTimeProps) => {\n  const [time, setTime] = useControllableState<Date>({\n    defaultProp: defaultTime,\n    prop: controlledTime,\n    onChange: onTimeChange,\n  });\n\n  useEffect(() => {\n    if (controlledTime) {\n      return;\n    }\n\n    const interval = setInterval(() => {\n      setTime(new Date());\n    }, 1000);\n\n    return () => clearInterval(interval);\n  }, [setTime, controlledTime]);\n\n  return (\n    <RelativeTimeContext.Provider\n      value={{\n        time: time ?? defaultTime,\n        dateFormatOptions,\n        timeFormatOptions,\n      }}\n    >\n      <div className={cn(\"grid gap-2\", className)} {...props} />\n    </RelativeTimeContext.Provider>\n  );\n};\n\nexport type RelativeTimeZoneProps = HTMLAttributes<HTMLDivElement> & {\n  zone: string;\n  dateFormatOptions?: Intl.DateTimeFormatOptions;\n  timeFormatOptions?: Intl.DateTimeFormatOptions;\n};\n\nexport type RelativeTimeZoneContextType = {\n  zone: string;\n};\n\nconst RelativeTimeZoneContext = createContext<RelativeTimeZoneContextType>({\n  zone: \"UTC\",\n});\n\nexport const RelativeTimeZone = ({\n  zone,\n  className,\n  ...props\n}: RelativeTimeZoneProps) => (\n  <RelativeTimeZoneContext.Provider value={{ zone }}>\n    <div\n      className={cn(\n        \"flex items-center justify-between gap-1.5 text-xs\",\n        className\n      )}\n      {...props}\n    />\n  </RelativeTimeZoneContext.Provider>\n);\n\nexport type RelativeTimeZoneDisplayProps = HTMLAttributes<HTMLDivElement>;\n\nexport const RelativeTimeZoneDisplay = ({\n  className,\n  ...props\n}: RelativeTimeZoneDisplayProps) => {\n  const { time, timeFormatOptions } = useContext(RelativeTimeContext);\n  const { zone } = useContext(RelativeTimeZoneContext);\n  const display = formatTime(time, zone, timeFormatOptions);\n\n  return (\n    <div\n      className={cn(\"pl-8 text-muted-foreground tabular-nums\", className)}\n      {...props}\n    >\n      {display}\n    </div>\n  );\n};\n\nexport type RelativeTimeZoneDateProps = HTMLAttributes<HTMLDivElement>;\n\nexport const RelativeTimeZoneDate = ({\n  className,\n  ...props\n}: RelativeTimeZoneDateProps) => {\n  const { time, dateFormatOptions } = useContext(RelativeTimeContext);\n  const { zone } = useContext(RelativeTimeZoneContext);\n  const display = formatDate(time, zone, dateFormatOptions);\n\n  return <div {...props}>{display}</div>;\n};\n\nexport type RelativeTimeZoneLabelProps = HTMLAttributes<HTMLDivElement>;\n\nexport const RelativeTimeZoneLabel = ({\n  className,\n  ...props\n}: RelativeTimeZoneLabelProps) => (\n  <div\n    className={cn(\n      \"flex h-4 items-center justify-center rounded-xs bg-secondary px-1.5 font-mono\",\n      className\n    )}\n    {...props}\n  />\n);\n","target":"components/kibo-ui/relative-time/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"sandbox","type":"registry:ui","title":"sandbox","description":"The sandbox component allows you to preview and test components in a sandboxed environment.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@codesandbox/sandpack-react"],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport type {\n  CodeEditorProps,\n  PreviewProps,\n  SandpackLayoutProps,\n  SandpackProviderProps,\n} from \"@codesandbox/sandpack-react\";\nimport {\n  SandpackCodeEditor,\n  SandpackConsole,\n  SandpackFileExplorer,\n  SandpackLayout,\n  SandpackPreview,\n  SandpackProvider,\n} from \"@codesandbox/sandpack-react\";\nimport type {\n  ButtonHTMLAttributes,\n  ComponentProps,\n  HTMLAttributes,\n  ReactNode,\n} from \"react\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useState,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type SandboxProviderProps = SandpackProviderProps;\n\nexport const SandboxProvider = ({\n  className,\n  ...props\n}: SandpackProviderProps): ReactNode => (\n  <div className={cn(\"size-full\", className)}>\n    <SandpackProvider className=\"!size-full !max-h-none\" {...props} />\n  </div>\n);\n\nexport type SandboxLayoutProps = SandpackLayoutProps;\n\nexport const SandboxLayout = ({\n  className,\n  ...props\n}: SandpackLayoutProps): ReactNode => (\n  <SandpackLayout\n    className={cn(\n      \"!rounded-none !border-none !bg-transparent !h-full\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type SandboxTabsContextValue = {\n  selectedTab: string | undefined;\n  setSelectedTab: (value: string) => void;\n};\n\nconst SandboxTabsContext = createContext<SandboxTabsContextValue | undefined>(\n  undefined\n);\n\nconst useSandboxTabsContext = () => {\n  const context = useContext(SandboxTabsContext);\n\n  if (!context) {\n    throw new Error(\n      \"SandboxTabs components must be used within a SandboxTabsProvider\"\n    );\n  }\n\n  return context;\n};\n\nexport type SandboxTabsProps = HTMLAttributes<HTMLDivElement> & {\n  defaultValue?: string;\n  value?: string;\n  onValueChange?: (value: string) => void;\n};\n\nexport const SandboxTabs = ({\n  className,\n  defaultValue,\n  value,\n  onValueChange,\n  ...props\n}: SandboxTabsProps): ReactNode => {\n  const [selectedTab, setSelectedTabState] = useState(value || defaultValue);\n\n  useEffect(() => {\n    if (value !== undefined) {\n      setSelectedTabState(value);\n    }\n  }, [value]);\n\n  const setSelectedTab = useCallback(\n    (newValue: string) => {\n      if (value === undefined) {\n        setSelectedTabState(newValue);\n      }\n      onValueChange?.(newValue);\n    },\n    [value, onValueChange]\n  );\n\n  return (\n    <SandboxTabsContext.Provider value={{ selectedTab, setSelectedTab }}>\n      <div\n        className={cn(\n          \"group relative flex size-full flex-col overflow-hidden rounded-lg border text-sm\",\n          className\n        )}\n        {...props}\n        data-selected={selectedTab}\n      >\n        {props.children}\n      </div>\n    </SandboxTabsContext.Provider>\n  );\n};\n\nexport type SandboxTabsListProps = HTMLAttributes<HTMLDivElement>;\n\nexport const SandboxTabsList = ({\n  className,\n  ...props\n}: SandboxTabsListProps): ReactNode => (\n  <div\n    className={cn(\n      \"inline-flex w-full shrink-0 items-center justify-start border-b bg-secondary p-2 text-muted-foreground\",\n      className\n    )}\n    role=\"tablist\"\n    {...props}\n  />\n);\n\nexport type SandboxTabsTriggerProps = Omit<\n  ButtonHTMLAttributes<HTMLButtonElement>,\n  \"onClick\"\n> & {\n  value: string;\n};\n\nexport const SandboxTabsTrigger = ({\n  className,\n  value,\n  ...props\n}: SandboxTabsTriggerProps): ReactNode => {\n  const { selectedTab, setSelectedTab } = useSandboxTabsContext();\n\n  const handleClick = useCallback(() => {\n    setSelectedTab(value);\n  }, [setSelectedTab, value]);\n\n  return (\n    <button\n      aria-selected={selectedTab === value}\n      className={cn(\n        \"inline-flex items-center justify-center gap-1.5 whitespace-nowrap rounded-md px-3 py-1 font-medium text-sm ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow\",\n        className\n      )}\n      data-state={selectedTab === value ? \"active\" : \"inactive\"}\n      onClick={handleClick}\n      role=\"tab\"\n      {...props}\n    />\n  );\n};\n\nexport type SandboxTabsContentProps = HTMLAttributes<HTMLDivElement> & {\n  value: string;\n};\n\nexport const SandboxTabsContent = ({\n  className,\n  value,\n  ...props\n}: SandboxTabsContentProps): ReactNode => {\n  const { selectedTab } = useSandboxTabsContext();\n\n  return (\n    <div\n      aria-hidden={selectedTab !== value}\n      className={cn(\n        \"flex-1 overflow-y-auto ring-offset-background transition-opacity duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n        selectedTab === value\n          ? \"h-auto w-auto opacity-100\"\n          : \"pointer-events-none absolute h-0 w-0 opacity-0\",\n        className\n      )}\n      data-state={selectedTab === value ? \"active\" : \"inactive\"}\n      role=\"tabpanel\"\n      {...props}\n    />\n  );\n};\n\nexport type SandboxCodeEditorProps = CodeEditorProps;\n\nexport const SandboxCodeEditor = ({\n  showTabs = false,\n  ...props\n}: SandboxCodeEditorProps): ReactNode => (\n  <SandpackCodeEditor showTabs={showTabs} {...props} />\n);\n\nexport type SandboxConsoleProps = Parameters<typeof SandpackConsole>[0];\n\nexport const SandboxConsole = ({\n  className,\n  ...props\n}: SandboxConsoleProps): ReactNode => (\n  <SandpackConsole className={cn(\"h-full\", className)} {...props} />\n);\n\nexport type SandboxPreviewProps = PreviewProps & {\n  className?: string;\n};\n\nexport const SandboxPreview = ({\n  className,\n  showOpenInCodeSandbox = false,\n  ...props\n}: SandboxPreviewProps): ReactNode => (\n  <SandpackPreview\n    className={cn(\"h-full\", className)}\n    showOpenInCodeSandbox={showOpenInCodeSandbox}\n    {...props}\n  />\n);\n\nexport type SandboxFileExplorerProps = ComponentProps<\n  typeof SandpackFileExplorer\n>;\n\nexport const SandboxFileExplorer = ({\n  autoHiddenFiles = true,\n  className,\n  ...props\n}: SandboxFileExplorerProps): ReactNode => (\n  <SandpackFileExplorer\n    autoHiddenFiles={autoHiddenFiles}\n    className={cn(\"h-full\", className)}\n    {...props}\n  />\n);\n","target":"components/kibo-ui/sandbox/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"snippet","type":"registry:ui","title":"snippet","description":"Snippet is a component that allows you to display and copy code in a tabbed interface.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["lucide-react"],"devDependencies":[],"registryDependencies":["button","tabs"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { CheckIcon, CopyIcon } from \"lucide-react\";\nimport {\n  type ComponentProps,\n  cloneElement,\n  type HTMLAttributes,\n  type ReactElement,\n  useState,\n} from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { cn } from \"@/lib/utils\";\n\nexport type SnippetProps = ComponentProps<typeof Tabs>;\n\nexport const Snippet = ({ className, ...props }: SnippetProps) => (\n  <Tabs\n    className={cn(\n      \"group w-full gap-0 overflow-hidden rounded-md border\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type SnippetHeaderProps = HTMLAttributes<HTMLDivElement>;\n\nexport const SnippetHeader = ({ className, ...props }: SnippetHeaderProps) => (\n  <div\n    className={cn(\n      \"flex flex-row items-center justify-between border-b bg-secondary p-1\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type SnippetCopyButtonProps = ComponentProps<typeof Button> & {\n  value: string;\n  onCopy?: () => void;\n  onError?: (error: Error) => void;\n  timeout?: number;\n};\n\nexport const SnippetCopyButton = ({\n  asChild,\n  value,\n  onCopy,\n  onError,\n  timeout = 2000,\n  children,\n  ...props\n}: SnippetCopyButtonProps) => {\n  const [isCopied, setIsCopied] = useState(false);\n\n  const copyToClipboard = () => {\n    if (\n      typeof window === \"undefined\" ||\n      !navigator.clipboard.writeText ||\n      !value\n    ) {\n      return;\n    }\n\n    navigator.clipboard.writeText(value).then(() => {\n      setIsCopied(true);\n      onCopy?.();\n\n      setTimeout(() => setIsCopied(false), timeout);\n    }, onError);\n  };\n\n  if (asChild) {\n    return cloneElement(children as ReactElement, {\n      // @ts-expect-error - we know this is a button\n      onClick: copyToClipboard,\n    });\n  }\n\n  const icon = isCopied ? <CheckIcon size={14} /> : <CopyIcon size={14} />;\n\n  return (\n    <Button\n      className=\"opacity-0 transition-opacity group-hover:opacity-100\"\n      onClick={copyToClipboard}\n      size=\"icon\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children ?? icon}\n    </Button>\n  );\n};\n\nexport type SnippetTabsListProps = ComponentProps<typeof TabsList>;\n\nexport const SnippetTabsList = TabsList;\n\nexport type SnippetTabsTriggerProps = ComponentProps<typeof TabsTrigger>;\n\nexport const SnippetTabsTrigger = ({\n  className,\n  ...props\n}: SnippetTabsTriggerProps) => (\n  <TabsTrigger className={cn(\"gap-1.5\", className)} {...props} />\n);\n\nexport type SnippetTabsContentProps = ComponentProps<typeof TabsContent>;\n\nexport const SnippetTabsContent = ({\n  className,\n  children,\n  ...props\n}: SnippetTabsContentProps) => (\n  <TabsContent\n    asChild\n    className={cn(\"mt-0 bg-background p-4 text-sm\", className)}\n    {...props}\n  >\n    <pre className=\"truncate\">{children}</pre>\n  </TabsContent>\n);\n","target":"components/kibo-ui/snippet/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"spinner","type":"registry:ui","title":"spinner","description":"A spinner is a visual indicator that shows progress or activity.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["lucide-react"],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"import { Spinner as ShadcnSpinner } from \"@repo/shadcn-ui/components/ui/spinner\";\nimport {\n  LoaderCircleIcon,\n  LoaderIcon,\n  LoaderPinwheelIcon,\n  type LucideProps,\n} from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\ntype SpinnerVariantProps = Omit<SpinnerProps, \"variant\">;\n\nconst Throbber = ({ className, ...props }: SpinnerVariantProps) => (\n  <LoaderIcon className={cn(\"animate-spin\", className)} {...props} />\n);\n\nconst Pinwheel = ({ className, ...props }: SpinnerVariantProps) => (\n  <LoaderPinwheelIcon className={cn(\"animate-spin\", className)} {...props} />\n);\n\nconst CircleFilled = ({\n  className,\n  size = 24,\n  ...props\n}: SpinnerVariantProps) => (\n  <div className=\"relative\" style={{ width: size, height: size }}>\n    <div className=\"absolute inset-0 rotate-180\">\n      <LoaderCircleIcon\n        className={cn(\"animate-spin\", className, \"text-foreground opacity-20\")}\n        size={size}\n        {...props}\n      />\n    </div>\n    <LoaderCircleIcon\n      className={cn(\"relative animate-spin\", className)}\n      size={size}\n      {...props}\n    />\n  </div>\n);\n\nconst Ellipsis = ({ size = 24, ...props }: SpinnerVariantProps) => {\n  return (\n    <svg\n      height={size}\n      viewBox=\"0 0 24 24\"\n      width={size}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <title>Loading...</title>\n      <circle cx=\"4\" cy=\"12\" fill=\"currentColor\" r=\"2\">\n        <animate\n          attributeName=\"cy\"\n          begin=\"0;ellipsis3.end+0.25s\"\n          calcMode=\"spline\"\n          dur=\"0.6s\"\n          id=\"ellipsis1\"\n          keySplines=\".33,.66,.66,1;.33,0,.66,.33\"\n          values=\"12;6;12\"\n        />\n      </circle>\n      <circle cx=\"12\" cy=\"12\" fill=\"currentColor\" r=\"2\">\n        <animate\n          attributeName=\"cy\"\n          begin=\"ellipsis1.begin+0.1s\"\n          calcMode=\"spline\"\n          dur=\"0.6s\"\n          keySplines=\".33,.66,.66,1;.33,0,.66,.33\"\n          values=\"12;6;12\"\n        />\n      </circle>\n      <circle cx=\"20\" cy=\"12\" fill=\"currentColor\" r=\"2\">\n        <animate\n          attributeName=\"cy\"\n          begin=\"ellipsis1.begin+0.2s\"\n          calcMode=\"spline\"\n          dur=\"0.6s\"\n          id=\"ellipsis3\"\n          keySplines=\".33,.66,.66,1;.33,0,.66,.33\"\n          values=\"12;6;12\"\n        />\n      </circle>\n    </svg>\n  );\n};\n\nconst Ring = ({ size = 24, ...props }: SpinnerVariantProps) => (\n  <svg\n    height={size}\n    stroke=\"currentColor\"\n    viewBox=\"0 0 44 44\"\n    width={size}\n    xmlns=\"http://www.w3.org/2000/svg\"\n    {...props}\n  >\n    <title>Loading...</title>\n    <g fill=\"none\" fillRule=\"evenodd\" strokeWidth=\"2\">\n      <circle cx=\"22\" cy=\"22\" r=\"1\">\n        <animate\n          attributeName=\"r\"\n          begin=\"0s\"\n          calcMode=\"spline\"\n          dur=\"1.8s\"\n          keySplines=\"0.165, 0.84, 0.44, 1\"\n          keyTimes=\"0; 1\"\n          repeatCount=\"indefinite\"\n          values=\"1; 20\"\n        />\n        <animate\n          attributeName=\"stroke-opacity\"\n          begin=\"0s\"\n          calcMode=\"spline\"\n          dur=\"1.8s\"\n          keySplines=\"0.3, 0.61, 0.355, 1\"\n          keyTimes=\"0; 1\"\n          repeatCount=\"indefinite\"\n          values=\"1; 0\"\n        />\n      </circle>\n      <circle cx=\"22\" cy=\"22\" r=\"1\">\n        <animate\n          attributeName=\"r\"\n          begin=\"-0.9s\"\n          calcMode=\"spline\"\n          dur=\"1.8s\"\n          keySplines=\"0.165, 0.84, 0.44, 1\"\n          keyTimes=\"0; 1\"\n          repeatCount=\"indefinite\"\n          values=\"1; 20\"\n        />\n        <animate\n          attributeName=\"stroke-opacity\"\n          begin=\"-0.9s\"\n          calcMode=\"spline\"\n          dur=\"1.8s\"\n          keySplines=\"0.3, 0.61, 0.355, 1\"\n          keyTimes=\"0; 1\"\n          repeatCount=\"indefinite\"\n          values=\"1; 0\"\n        />\n      </circle>\n    </g>\n  </svg>\n);\n\nconst Bars = ({ size = 24, ...props }: SpinnerVariantProps) => (\n  <svg\n    height={size}\n    viewBox=\"0 0 24 24\"\n    width={size}\n    xmlns=\"http://www.w3.org/2000/svg\"\n    {...props}\n  >\n    <title>Loading...</title>\n    <style>{`\n      .spinner-bar {\n        animation: spinner-bars-animation .8s linear infinite;\n        animation-delay: -.8s;\n      }\n      .spinner-bars-2 {\n        animation-delay: -.65s;\n      }\n      .spinner-bars-3 {\n        animation-delay: -0.5s;\n      }\n      @keyframes spinner-bars-animation {\n        0% {\n          y: 1px;\n          height: 22px;\n        }\n        93.75% {\n          y: 5px;\n          height: 14px;\n          opacity: 0.2;\n        }\n      }\n    `}</style>\n    <rect\n      className=\"spinner-bar\"\n      fill=\"currentColor\"\n      height=\"22\"\n      width=\"6\"\n      x=\"1\"\n      y=\"1\"\n    />\n    <rect\n      className=\"spinner-bar spinner-bars-2\"\n      fill=\"currentColor\"\n      height=\"22\"\n      width=\"6\"\n      x=\"9\"\n      y=\"1\"\n    />\n    <rect\n      className=\"spinner-bar spinner-bars-3\"\n      fill=\"currentColor\"\n      height=\"22\"\n      width=\"6\"\n      x=\"17\"\n      y=\"1\"\n    />\n  </svg>\n);\n\nconst Infinite = ({ size = 24, ...props }: SpinnerVariantProps) => (\n  <svg\n    height={size}\n    preserveAspectRatio=\"xMidYMid\"\n    viewBox=\"0 0 100 100\"\n    width={size}\n    xmlns=\"http://www.w3.org/2000/svg\"\n    {...props}\n  >\n    <title>Loading...</title>\n    <path\n      d=\"M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40 C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeDasharray=\"205.271142578125 51.317785644531256\"\n      strokeLinecap=\"round\"\n      strokeWidth=\"10\"\n      style={{\n        transform: \"scale(0.8)\",\n        transformOrigin: \"50px 50px\",\n      }}\n    >\n      <animate\n        attributeName=\"stroke-dashoffset\"\n        dur=\"2s\"\n        keyTimes=\"0;1\"\n        repeatCount=\"indefinite\"\n        values=\"0;256.58892822265625\"\n      />\n    </path>\n  </svg>\n);\n\nexport type SpinnerProps = LucideProps & {\n  variant?:\n    | \"default\"\n    | \"throbber\"\n    | \"pinwheel\"\n    | \"circle-filled\"\n    | \"ellipsis\"\n    | \"ring\"\n    | \"bars\"\n    | \"infinite\";\n};\n\nexport const Spinner = ({ variant, ...props }: SpinnerProps) => {\n  switch (variant) {\n    case \"throbber\":\n      return <Throbber {...props} />;\n    case \"pinwheel\":\n      return <Pinwheel {...props} />;\n    case \"circle-filled\":\n      return <CircleFilled {...props} />;\n    case \"ellipsis\":\n      return <Ellipsis {...props} />;\n    case \"ring\":\n      return <Ring {...props} />;\n    case \"bars\":\n      return <Bars {...props} />;\n    case \"infinite\":\n      return <Infinite {...props} />;\n    default:\n      return (\n        <ShadcnSpinner className={cn(\"size-6\", props.className)} {...props} />\n      );\n  }\n};\n","target":"components/kibo-ui/spinner/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"status","type":"registry:ui","title":"status","description":"Status components are used to display the uptime of a service.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":[],"devDependencies":[],"registryDependencies":["badge"],"files":[{"type":"registry:ui","path":"index.tsx","content":"import type { ComponentProps, HTMLAttributes } from \"react\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { cn } from \"@/lib/utils\";\n\nexport type StatusProps = ComponentProps<typeof Badge> & {\n  status: \"online\" | \"offline\" | \"maintenance\" | \"degraded\";\n};\n\nexport const Status = ({ className, status, ...props }: StatusProps) => (\n  <Badge\n    className={cn(\"flex items-center gap-2\", \"group\", status, className)}\n    variant=\"secondary\"\n    {...props}\n  />\n);\n\nexport type StatusIndicatorProps = HTMLAttributes<HTMLSpanElement>;\n\nexport const StatusIndicator = ({\n  className,\n  ...props\n}: StatusIndicatorProps) => (\n  <span className=\"relative flex h-2 w-2\" {...props}>\n    <span\n      className={cn(\n        \"absolute inline-flex h-full w-full animate-ping rounded-full opacity-75\",\n        \"group-[.online]:bg-emerald-500\",\n        \"group-[.offline]:bg-red-500\",\n        \"group-[.maintenance]:bg-blue-500\",\n        \"group-[.degraded]:bg-amber-500\"\n      )}\n    />\n    <span\n      className={cn(\n        \"relative inline-flex h-2 w-2 rounded-full\",\n        \"group-[.online]:bg-emerald-500\",\n        \"group-[.offline]:bg-red-500\",\n        \"group-[.maintenance]:bg-blue-500\",\n        \"group-[.degraded]:bg-amber-500\"\n      )}\n    />\n  </span>\n);\n\nexport type StatusLabelProps = HTMLAttributes<HTMLSpanElement>;\n\nexport const StatusLabel = ({\n  className,\n  children,\n  ...props\n}: StatusLabelProps) => (\n  <span className={cn(\"text-muted-foreground\", className)} {...props}>\n    {children ?? (\n      <>\n        <span className=\"hidden group-[.online]:block\">Online</span>\n        <span className=\"hidden group-[.offline]:block\">Offline</span>\n        <span className=\"hidden group-[.maintenance]:block\">Maintenance</span>\n        <span className=\"hidden group-[.degraded]:block\">Degraded</span>\n      </>\n    )}\n  </span>\n);\n","target":"components/kibo-ui/status/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"stories","type":"registry:ui","title":"stories","description":"A composable Stories component - a carousel of individual video cards like you'd see on Facebook.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":[],"devDependencies":[],"registryDependencies":["avatar","carousel"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport type {\n  ComponentProps,\n  HTMLAttributes,\n  VideoHTMLAttributes,\n} from \"react\";\nimport { useEffect, useRef } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport {\n  Carousel,\n  CarouselContent,\n  CarouselItem,\n} from \"@/components/ui/carousel\";\nimport { cn } from \"@/lib/utils\";\n\nexport type StoriesProps = ComponentProps<typeof Carousel>;\n\nexport const Stories = ({ className, opts, ...props }: StoriesProps) => (\n  <Carousel\n    className={cn(\"w-full\", className)}\n    opts={{\n      align: \"start\",\n      loop: false,\n      dragFree: true,\n      ...opts,\n    }}\n    {...props}\n  />\n);\n\nexport type StoriesContentProps = ComponentProps<typeof CarouselContent>;\n\nexport const StoriesContent = ({\n  className,\n  ...props\n}: StoriesContentProps) => (\n  <CarouselContent className={cn(\"-ml-2 gap-2\", className)} {...props} />\n);\n\nexport type StoryProps = HTMLAttributes<HTMLDivElement>;\n\nexport const Story = ({ className, ...props }: StoryProps) => (\n  <CarouselItem className={cn(\"basis-0 pl-2 md:pl-4\", className)}>\n    <div\n      className={cn(\n        \"group relative overflow-hidden rounded-xl bg-muted/40\",\n        \"cursor-pointer transition-all duration-200\",\n        \"hover:scale-[1.02] hover:shadow-lg\",\n        \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n        className\n      )}\n      role=\"button\"\n      tabIndex={0}\n      {...props}\n    />\n  </CarouselItem>\n);\n\nexport type StoryVideoProps = VideoHTMLAttributes<HTMLVideoElement>;\n\nconst tRegex = /t=(\\d+(?:\\.\\d+)?)/;\n\nexport const StoryVideo = ({ className, ...props }: StoryVideoProps) => {\n  const videoRef = useRef<HTMLVideoElement>(null);\n  const initialTimeRef = useRef<number>(0);\n\n  // Parse the initial time from the src attribute (e.g., #t=20)\n  useEffect(() => {\n    const src = (props.src ?? \"\") as string;\n    let initialTime = 0;\n    if (typeof src === \"string\") {\n      const hashIndex = src.indexOf(\"#\");\n      if (hashIndex !== -1) {\n        const hash = src.slice(hashIndex + 1);\n        // Look for t=number or t=start,end\n        const tMatch = hash.match(tRegex);\n        if (tMatch) {\n          initialTime = Number.parseFloat(tMatch[1]);\n        }\n      }\n    }\n    initialTimeRef.current = initialTime;\n  }, [props.src]);\n\n  const handleMouseOver = () => {\n    videoRef.current?.play();\n  };\n\n  const handleMouseOut = () => {\n    if (videoRef.current) {\n      videoRef.current.pause();\n      videoRef.current.currentTime = initialTimeRef.current;\n    }\n  };\n\n  const handleFocus = () => {\n    videoRef.current?.play();\n  };\n\n  const handleBlur = () => {\n    if (videoRef.current) {\n      videoRef.current.pause();\n      videoRef.current.currentTime = initialTimeRef.current;\n    }\n  };\n\n  return (\n    <video\n      className={cn(\n        \"absolute inset-0 size-full object-cover\",\n        \"transition-opacity duration-200\",\n        \"group-hover:opacity-90\",\n        className\n      )}\n      loop\n      muted\n      onBlur={handleBlur}\n      onFocus={handleFocus}\n      onMouseOut={handleMouseOut}\n      onMouseOver={handleMouseOver}\n      preload=\"metadata\"\n      ref={videoRef}\n      tabIndex={0}\n      {...props}\n    />\n  );\n};\n\nexport type StoryImageProps = ComponentProps<\"img\"> & {\n  alt: string;\n};\n\nexport const StoryImage = ({ className, alt, ...props }: StoryImageProps) => (\n  // biome-ignore lint/performance/noImgElement: \"Kibo UI is framework agnostic\"\n  <img\n    alt={alt}\n    className={cn(\n      \"absolute inset-0 h-full w-full object-cover\",\n      \"transition-opacity duration-200\",\n      \"group-hover:opacity-90\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type StoryAuthorProps = HTMLAttributes<HTMLDivElement>;\n\nexport const StoryAuthor = ({\n  className,\n  children,\n  ...props\n}: StoryAuthorProps) => (\n  <div\n    className={cn(\n      \"absolute right-0 bottom-0 left-0 z-10\",\n      \"p-3 text-white\",\n      className\n    )}\n    {...props}\n  >\n    <div className=\"flex items-center gap-2\">{children}</div>\n  </div>\n);\n\nexport type StoryAuthorImageProps = ComponentProps<typeof Avatar> & {\n  src?: string;\n  name?: string;\n  fallback?: string;\n};\n\nexport const StoryAuthorImage = ({\n  src,\n  fallback,\n  name,\n  className,\n  ...props\n}: StoryAuthorImageProps) => (\n  <Avatar className={cn(\"size-6 border border-white/20\", className)} {...props}>\n    {src && <AvatarImage alt={name} src={src} />}\n    <AvatarFallback className=\"bg-white/10 text-white text-xs\">\n      {fallback || name?.charAt(0)?.toUpperCase()}\n    </AvatarFallback>\n  </Avatar>\n);\n\nexport type StoryAuthorNameProps = HTMLAttributes<HTMLSpanElement>;\n\nexport const StoryAuthorName = ({\n  className,\n  ...props\n}: StoryAuthorNameProps) => (\n  <span className={cn(\"truncate font-medium text-sm\", className)} {...props} />\n);\n\nexport type StoryTitleProps = HTMLAttributes<HTMLDivElement>;\n\nexport const StoryTitle = ({ className, ...props }: StoryTitleProps) => (\n  <div\n    className={cn(\n      \"absolute top-0 right-0 left-0 z-10\",\n      \"p-3 text-white\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type StoryOverlayProps = HTMLAttributes<HTMLDivElement> & {\n  side?: \"top\" | \"bottom\";\n};\n\nexport const StoryOverlay = ({\n  className,\n  side = \"bottom\",\n  ...props\n}: StoryOverlayProps) => {\n  const positionClasses =\n    side === \"top\" ? \"top-0 bg-gradient-to-b\" : \"bottom-0 bg-gradient-to-t\";\n\n  return (\n    <div\n      className={cn(\n        \"absolute right-0 left-0 h-10 from-black/20 to-transparent\",\n        positionClasses,\n        className\n      )}\n      {...props}\n    />\n  );\n};\n","target":"components/kibo-ui/stories/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"table","type":"registry:ui","title":"table","description":"Table views are used to display data in a tabular format. They are useful for displaying large amounts of data in a structured way.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@tanstack/react-table","jotai","lucide-react"],"devDependencies":[],"registryDependencies":["button","dropdown-menu","table"],"files":[{"type":"registry:ui","path":"index.tsx","content":"import type {\n  Cell,\n  Column,\n  ColumnDef,\n  Header,\n  HeaderGroup,\n  Row,\n  SortingState,\n  Table,\n} from \"@tanstack/react-table\";\nimport {\n  flexRender,\n  getCoreRowModel,\n  getSortedRowModel,\n  useReactTable,\n} from \"@tanstack/react-table\";\nimport { atom, useAtom } from \"jotai\";\nimport { ArrowDownIcon, ArrowUpIcon, ChevronsUpDownIcon } from \"lucide-react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { createContext, memo, useCallback, useContext } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport {\n  TableBody as TableBodyRaw,\n  TableCell as TableCellRaw,\n  TableHeader as TableHeaderRaw,\n  TableHead as TableHeadRaw,\n  Table as TableRaw,\n  TableRow as TableRowRaw,\n} from \"@/components/ui/table\";\nimport { cn } from \"@/lib/utils\";\n\nexport type { ColumnDef } from \"@tanstack/react-table\";\n\nconst sortingAtom = atom<SortingState>([]);\n\nexport const TableContext = createContext<{\n  data: unknown[];\n  columns: ColumnDef<unknown, unknown>[];\n  table: Table<unknown> | null;\n}>({\n  data: [],\n  columns: [],\n  table: null,\n});\n\nexport type TableProviderProps<TData, TValue> = {\n  columns: ColumnDef<TData, TValue>[];\n  data: TData[];\n  children: ReactNode;\n  className?: string;\n};\n\nexport function TableProvider<TData, TValue>({\n  columns,\n  data,\n  children,\n  className,\n}: TableProviderProps<TData, TValue>) {\n  const [sorting, setSorting] = useAtom(sortingAtom);\n  const table = useReactTable({\n    data,\n    columns,\n    getCoreRowModel: getCoreRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    onSortingChange: (updater) => {\n      // @ts-expect-error updater is a function that returns a sorting object\n      const newSorting = updater(sorting);\n\n      setSorting(newSorting);\n    },\n    state: {\n      sorting,\n    },\n  });\n\n  return (\n    <TableContext.Provider\n      value={{\n        data,\n        columns: columns as never,\n        table: table as never,\n      }}\n    >\n      <TableRaw className={className}>{children}</TableRaw>\n    </TableContext.Provider>\n  );\n}\n\nexport type TableHeadProps = {\n  header: Header<unknown, unknown>;\n  className?: string;\n};\n\nexport const TableHead = memo(({ header, className }: TableHeadProps) => (\n  <TableHeadRaw className={className} key={header.id}>\n    {header.isPlaceholder\n      ? null\n      : flexRender(header.column.columnDef.header, header.getContext())}\n  </TableHeadRaw>\n));\n\nTableHead.displayName = \"TableHead\";\n\nexport type TableHeaderGroupProps = {\n  headerGroup: HeaderGroup<unknown>;\n  children: (props: { header: Header<unknown, unknown> }) => ReactNode;\n};\n\nexport const TableHeaderGroup = ({\n  headerGroup,\n  children,\n}: TableHeaderGroupProps) => (\n  <TableRowRaw key={headerGroup.id}>\n    {headerGroup.headers.map((header) => children({ header }))}\n  </TableRowRaw>\n);\n\nexport type TableHeaderProps = {\n  className?: string;\n  children: (props: { headerGroup: HeaderGroup<unknown> }) => ReactNode;\n};\n\nexport const TableHeader = ({ className, children }: TableHeaderProps) => {\n  const { table } = useContext(TableContext);\n\n  return (\n    <TableHeaderRaw className={className}>\n      {table?.getHeaderGroups().map((headerGroup) => children({ headerGroup }))}\n    </TableHeaderRaw>\n  );\n};\n\nexport interface TableColumnHeaderProps<TData, TValue>\n  extends HTMLAttributes<HTMLDivElement> {\n  column: Column<TData, TValue>;\n  title: string;\n}\n\nexport function TableColumnHeader<TData, TValue>({\n  column,\n  title,\n  className,\n}: TableColumnHeaderProps<TData, TValue>) {\n  // Extract inline event handlers to prevent unnecessary re-renders\n  const handleSortAsc = useCallback(() => {\n    column.toggleSorting(false);\n  }, [column]);\n\n  const handleSortDesc = useCallback(() => {\n    column.toggleSorting(true);\n  }, [column]);\n\n  if (!column.getCanSort()) {\n    return <div className={cn(className)}>{title}</div>;\n  }\n\n  return (\n    <div className={cn(\"flex items-center space-x-2\", className)}>\n      <DropdownMenu>\n        <DropdownMenuTrigger asChild>\n          <Button\n            className=\"-ml-3 h-8 data-[state=open]:bg-accent\"\n            size=\"sm\"\n            variant=\"ghost\"\n          >\n            <span>{title}</span>\n            {column.getIsSorted() === \"desc\" ? (\n              <ArrowDownIcon className=\"ml-2 h-4 w-4\" />\n            ) : column.getIsSorted() === \"asc\" ? (\n              <ArrowUpIcon className=\"ml-2 h-4 w-4\" />\n            ) : (\n              <ChevronsUpDownIcon className=\"ml-2 h-4 w-4\" />\n            )}\n          </Button>\n        </DropdownMenuTrigger>\n        <DropdownMenuContent align=\"start\">\n          <DropdownMenuItem onClick={handleSortAsc}>\n            <ArrowUpIcon className=\"mr-2 h-3.5 w-3.5 text-muted-foreground/70\" />\n            Asc\n          </DropdownMenuItem>\n          <DropdownMenuItem onClick={handleSortDesc}>\n            <ArrowDownIcon className=\"mr-2 h-3.5 w-3.5 text-muted-foreground/70\" />\n            Desc\n          </DropdownMenuItem>\n        </DropdownMenuContent>\n      </DropdownMenu>\n    </div>\n  );\n}\n\nexport type TableCellProps = {\n  cell: Cell<unknown, unknown>;\n  className?: string;\n};\n\nexport const TableCell = ({ cell, className }: TableCellProps) => (\n  <TableCellRaw className={className}>\n    {flexRender(cell.column.columnDef.cell, cell.getContext())}\n  </TableCellRaw>\n);\n\nexport type TableRowProps = {\n  row: Row<unknown>;\n  children: (props: { cell: Cell<unknown, unknown> }) => ReactNode;\n  className?: string;\n};\n\nexport const TableRow = ({ row, children, className }: TableRowProps) => (\n  <TableRowRaw\n    className={className}\n    data-state={row.getIsSelected() && \"selected\"}\n    key={row.id}\n  >\n    {row.getVisibleCells().map((cell) => children({ cell }))}\n  </TableRowRaw>\n);\n\nexport type TableBodyProps = {\n  children: (props: { row: Row<unknown> }) => ReactNode;\n  className?: string;\n};\n\nexport const TableBody = ({ children, className }: TableBodyProps) => {\n  const { columns, table } = useContext(TableContext);\n  const rows = table?.getRowModel().rows;\n\n  return (\n    <TableBodyRaw className={className}>\n      {rows?.length ? (\n        rows.map((row) => children({ row }))\n      ) : (\n        <TableRowRaw>\n          <TableCellRaw className=\"h-24 text-center\" colSpan={columns.length}>\n            No results.\n          </TableCellRaw>\n        </TableRowRaw>\n      )}\n    </TableBodyRaw>\n  );\n};\n","target":"components/kibo-ui/table/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"tags","type":"registry:ui","title":"tags","description":"Tags are a way to apply multiple labels to an item.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["lucide-react"],"devDependencies":[],"registryDependencies":["badge","button","command","popover"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { XIcon } from \"lucide-react\";\nimport {\n  type ComponentProps,\n  createContext,\n  type MouseEventHandler,\n  type ReactNode,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n} from \"@/components/ui/command\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { cn } from \"@/lib/utils\";\n\ntype TagsContextType = {\n  value?: string;\n  setValue?: (value: string) => void;\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  width?: number;\n  setWidth?: (width: number) => void;\n};\n\nconst TagsContext = createContext<TagsContextType>({\n  value: undefined,\n  setValue: undefined,\n  open: false,\n  onOpenChange: () => {},\n  width: undefined,\n  setWidth: undefined,\n});\n\nconst useTagsContext = () => {\n  const context = useContext(TagsContext);\n\n  if (!context) {\n    throw new Error(\"useTagsContext must be used within a TagsProvider\");\n  }\n\n  return context;\n};\n\nexport type TagsProps = {\n  value?: string;\n  setValue?: (value: string) => void;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  children?: ReactNode;\n  className?: string;\n};\n\nexport const Tags = ({\n  value,\n  setValue,\n  open: controlledOpen,\n  onOpenChange: controlledOnOpenChange,\n  children,\n  className,\n}: TagsProps) => {\n  const [uncontrolledOpen, setUncontrolledOpen] = useState(false);\n  const [width, setWidth] = useState<number>();\n  const ref = useRef<HTMLDivElement>(null);\n\n  const open = controlledOpen ?? uncontrolledOpen;\n  const onOpenChange = controlledOnOpenChange ?? setUncontrolledOpen;\n\n  useEffect(() => {\n    if (!ref.current) {\n      return;\n    }\n\n    const resizeObserver = new ResizeObserver((entries) => {\n      setWidth(entries[0].contentRect.width);\n    });\n\n    resizeObserver.observe(ref.current);\n\n    return () => {\n      resizeObserver.disconnect();\n    };\n  }, []);\n\n  return (\n    <TagsContext.Provider\n      value={{ value, setValue, open, onOpenChange, width, setWidth }}\n    >\n      <Popover onOpenChange={onOpenChange} open={open}>\n        <div className={cn(\"relative w-full\", className)} ref={ref}>\n          {children}\n        </div>\n      </Popover>\n    </TagsContext.Provider>\n  );\n};\n\nexport type TagsTriggerProps = ComponentProps<typeof Button>;\n\nexport const TagsTrigger = ({\n  className,\n  children,\n  ...props\n}: TagsTriggerProps) => (\n  <PopoverTrigger asChild>\n    <Button\n      className={cn(\"h-auto w-full justify-between p-2\", className)}\n      // biome-ignore lint/a11y/useSemanticElements: \"Required\"\n      role=\"combobox\"\n      variant=\"outline\"\n      {...props}\n    >\n      <div className=\"flex flex-wrap items-center gap-1\">\n        {children}\n        <span className=\"px-2 py-px text-muted-foreground\">\n          Select a tag...\n        </span>\n      </div>\n    </Button>\n  </PopoverTrigger>\n);\n\nexport type TagsValueProps = ComponentProps<typeof Badge>;\n\nexport const TagsValue = ({\n  className,\n  children,\n  onRemove,\n  ...props\n}: TagsValueProps & { onRemove?: () => void }) => {\n  const handleRemove: MouseEventHandler<HTMLDivElement> = (event) => {\n    event.preventDefault();\n    event.stopPropagation();\n    onRemove?.();\n  };\n\n  return (\n    <Badge className={cn(\"flex items-center gap-2\", className)} {...props}>\n      {children}\n      {onRemove && (\n        // biome-ignore lint/a11y/noStaticElementInteractions: \"This is a clickable badge\"\n        // biome-ignore lint/a11y/useKeyWithClickEvents: \"This is a clickable badge\"\n        <div\n          className=\"size-auto cursor-pointer hover:text-muted-foreground\"\n          onClick={handleRemove}\n        >\n          <XIcon size={12} />\n        </div>\n      )}\n    </Badge>\n  );\n};\n\nexport type TagsContentProps = ComponentProps<typeof PopoverContent>;\n\nexport const TagsContent = ({\n  className,\n  children,\n  ...props\n}: TagsContentProps) => {\n  const { width } = useTagsContext();\n\n  return (\n    <PopoverContent\n      className={cn(\"p-0\", className)}\n      style={{ width }}\n      {...props}\n    >\n      <Command>{children}</Command>\n    </PopoverContent>\n  );\n};\n\nexport type TagsInputProps = ComponentProps<typeof CommandInput>;\n\nexport const TagsInput = ({ className, ...props }: TagsInputProps) => (\n  <CommandInput className={cn(\"h-9\", className)} {...props} />\n);\n\nexport type TagsListProps = ComponentProps<typeof CommandList>;\n\nexport const TagsList = ({ className, ...props }: TagsListProps) => (\n  <CommandList className={cn(\"max-h-[200px]\", className)} {...props} />\n);\n\nexport type TagsEmptyProps = ComponentProps<typeof CommandEmpty>;\n\nexport const TagsEmpty = ({\n  children,\n  className,\n  ...props\n}: TagsEmptyProps) => (\n  <CommandEmpty {...props}>{children ?? \"No tags found.\"}</CommandEmpty>\n);\n\nexport type TagsGroupProps = ComponentProps<typeof CommandGroup>;\n\nexport const TagsGroup = CommandGroup;\n\nexport type TagsItemProps = ComponentProps<typeof CommandItem>;\n\nexport const TagsItem = ({ className, ...props }: TagsItemProps) => (\n  <CommandItem\n    className={cn(\"cursor-pointer items-center justify-between\", className)}\n    {...props}\n  />\n);\n","target":"components/kibo-ui/tags/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"theme-switcher","type":"registry:ui","title":"theme-switcher","description":"A component to switch between light, dark and system theme.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@radix-ui/react-use-controllable-state","lucide-react","motion"],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport { Monitor, Moon, Sun } from \"lucide-react\";\nimport { motion } from \"motion/react\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst themes = [\n  {\n    key: \"system\",\n    icon: Monitor,\n    label: \"System theme\",\n  },\n  {\n    key: \"light\",\n    icon: Sun,\n    label: \"Light theme\",\n  },\n  {\n    key: \"dark\",\n    icon: Moon,\n    label: \"Dark theme\",\n  },\n];\n\nexport type ThemeSwitcherProps = {\n  value?: \"light\" | \"dark\" | \"system\";\n  onChange?: (theme: \"light\" | \"dark\" | \"system\") => void;\n  defaultValue?: \"light\" | \"dark\" | \"system\";\n  className?: string;\n};\n\nexport const ThemeSwitcher = ({\n  value,\n  onChange,\n  defaultValue = \"system\",\n  className,\n}: ThemeSwitcherProps) => {\n  const [theme, setTheme] = useControllableState({\n    defaultProp: defaultValue,\n    prop: value,\n    onChange,\n  });\n  const [mounted, setMounted] = useState(false);\n\n  const handleThemeClick = useCallback(\n    (themeKey: \"light\" | \"dark\" | \"system\") => {\n      setTheme(themeKey);\n    },\n    [setTheme]\n  );\n\n  // Prevent hydration mismatch\n  useEffect(() => {\n    setMounted(true);\n  }, []);\n\n  if (!mounted) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\n        \"relative isolate flex h-8 rounded-full bg-background p-1 ring-1 ring-border\",\n        className\n      )}\n    >\n      {themes.map(({ key, icon: Icon, label }) => {\n        const isActive = theme === key;\n\n        return (\n          <button\n            aria-label={label}\n            className=\"relative h-6 w-6 rounded-full\"\n            key={key}\n            onClick={() => handleThemeClick(key as \"light\" | \"dark\" | \"system\")}\n            type=\"button\"\n          >\n            {isActive && (\n              <motion.div\n                className=\"absolute inset-0 rounded-full bg-secondary\"\n                layoutId=\"activeTheme\"\n                transition={{ type: \"spring\", duration: 0.5 }}\n              />\n            )}\n            <Icon\n              className={cn(\n                \"relative z-10 m-auto h-4 w-4\",\n                isActive ? \"text-foreground\" : \"text-muted-foreground\"\n              )}\n            />\n          </button>\n        );\n      })}\n    </div>\n  );\n};\n","target":"components/kibo-ui/theme-switcher/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"ticker","type":"registry:ui","title":"ticker","description":"A composable finance ticker for displaying symbols, prices and changes.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":[],"devDependencies":[],"registryDependencies":["avatar"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { createContext, memo, useContext, useMemo } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { cn } from \"@/lib/utils\";\n\ntype TickerContextValue = {\n  formatter: Intl.NumberFormat;\n};\n\nconst DEFAULT_CURRENCY = \"USD\";\nconst DEFAULT_LOCALE = \"en-US\";\n\nconst defaultFormatter = new Intl.NumberFormat(DEFAULT_LOCALE, {\n  style: \"currency\",\n  currency: DEFAULT_CURRENCY,\n  minimumFractionDigits: 2,\n  maximumFractionDigits: 2,\n});\n\nconst TickerContext = createContext<TickerContextValue>({\n  formatter: defaultFormatter,\n});\n\nexport const useTickerContext = () => useContext(TickerContext);\n\nexport type TickerProps = HTMLAttributes<HTMLButtonElement> & {\n  currency?: string;\n  locale?: string;\n};\n\nexport const Ticker = memo(\n  ({\n    children,\n    className,\n    currency = DEFAULT_CURRENCY,\n    locale = DEFAULT_LOCALE,\n    ...props\n  }: TickerProps & { children: ReactNode }) => {\n    const formatter = useMemo(() => {\n      try {\n        return new Intl.NumberFormat(locale, {\n          style: \"currency\",\n          currency: currency.toUpperCase(),\n          minimumFractionDigits: 2,\n          maximumFractionDigits: 2,\n        });\n      } catch {\n        return defaultFormatter;\n      }\n    }, [currency, locale]);\n\n    return (\n      <TickerContext.Provider value={{ formatter }}>\n        <button\n          className={cn(\n            \"inline-flex items-center gap-1.5 whitespace-nowrap align-middle\",\n            className\n          )}\n          type=\"button\"\n          {...props}\n        >\n          {children}\n        </button>\n      </TickerContext.Provider>\n    );\n  }\n);\nTicker.displayName = \"Ticker\";\n\nexport type TickerIconProps = HTMLAttributes<HTMLImageElement> & {\n  src?: string;\n  symbol?: string;\n  asChild?: boolean;\n};\n\nexport const TickerIcon = memo(\n  ({\n    src,\n    symbol,\n    className,\n    asChild,\n    children,\n    ...props\n  }: TickerIconProps) => {\n    if (asChild) {\n      return (\n        <div\n          className={cn(\n            \"overflow-hidden rounded-full border border-border bg-muted\",\n            className\n          )}\n        >\n          {children}\n        </div>\n      );\n    }\n\n    return (\n      <Avatar className={cn(\"size-7 border border-border bg-muted\", className)}>\n        <AvatarImage src={src} {...props} />\n        <AvatarFallback className=\"font-semibold text-muted-foreground text-sm\">\n          {symbol?.slice(0, 2).toUpperCase()}\n        </AvatarFallback>\n      </Avatar>\n    );\n  }\n);\nTickerIcon.displayName = \"TickerIcon\";\n\nexport type TickerSymbolProps = HTMLAttributes<HTMLSpanElement> & {\n  symbol: string;\n};\n\nexport const TickerSymbol = memo(\n  ({ symbol, className, ...props }: TickerSymbolProps) => (\n    <span className={cn(\"font-medium\", className)} {...props}>\n      {symbol.toUpperCase()}\n    </span>\n  )\n);\nTickerSymbol.displayName = \"TickerSymbol\";\n\nexport type TickerPriceProps = HTMLAttributes<HTMLSpanElement> & {\n  price: number;\n};\n\nexport const TickerPrice = memo(\n  ({ price, className, ...props }: TickerPriceProps) => {\n    const context = useTickerContext();\n\n    const formattedPrice = useMemo(\n      () => context.formatter.format(price),\n      [price, context]\n    );\n\n    return (\n      <span className={cn(\"text-muted-foreground\", className)} {...props}>\n        {formattedPrice}\n      </span>\n    );\n  }\n);\nTickerPrice.displayName = \"TickerPrice\";\n\nexport type TickerPriceChangeProps = HTMLAttributes<HTMLSpanElement> & {\n  change: number;\n  isPercent?: boolean;\n};\n\nexport const TickerPriceChange = memo(\n  ({ change, isPercent, className, ...props }: TickerPriceChangeProps) => {\n    const isPositiveChange = useMemo(() => change >= 0, [change]);\n    const context = useTickerContext();\n\n    const changeFormatted = useMemo(() => {\n      if (isPercent) {\n        return `${change.toFixed(2)}%`;\n      }\n      return context.formatter.format(change);\n    }, [change, isPercent, context]);\n\n    return (\n      <span\n        className={cn(\n          \"flex items-center gap-0.5\",\n          isPositiveChange\n            ? \"text-green-600 dark:text-green-500\"\n            : \"text-red-600 dark:text-red-500\",\n          className\n        )}\n        {...props}\n      >\n        <svg\n          aria-labelledby=\"ticker-change-icon-title\"\n          className={isPositiveChange ? \"\" : \"rotate-180\"}\n          fill=\"currentColor\"\n          height=\"12\"\n          role=\"img\"\n          viewBox=\"0 0 24 24\"\n          width=\"12\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <title id=\"ticker-change-icon-title\">\n            {isPositiveChange ? \"Up icon\" : \"Down icon\"}\n          </title>\n          <path d=\"M24 22h-24l12-20z\" />\n        </svg>\n        {changeFormatted}\n      </span>\n    );\n  }\n);\nTickerPriceChange.displayName = \"TickerPriceChange\";\n","target":"components/kibo-ui/ticker/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"tree","type":"registry:ui","title":"tree","description":"A composable tree component with animated expand/collapse and customizable nodes.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["motion","lucide-react"],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { ChevronRight, File, Folder, FolderOpen } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport {\n  type ComponentProps,\n  createContext,\n  type HTMLAttributes,\n  type ReactNode,\n  useCallback,\n  useContext,\n  useId,\n  useState,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ntype TreeContextType = {\n  expandedIds: Set<string>;\n  selectedIds: string[];\n  toggleExpanded: (nodeId: string) => void;\n  handleSelection: (nodeId: string, ctrlKey: boolean) => void;\n  showLines?: boolean;\n  showIcons?: boolean;\n  selectable?: boolean;\n  multiSelect?: boolean;\n  indent?: number;\n  animateExpand?: boolean;\n};\n\nconst TreeContext = createContext<TreeContextType | undefined>(undefined);\n\nconst useTree = () => {\n  const context = useContext(TreeContext);\n  if (!context) {\n    throw new Error(\"Tree components must be used within a TreeProvider\");\n  }\n  return context;\n};\n\ntype TreeNodeContextType = {\n  nodeId: string;\n  level: number;\n  isLast: boolean;\n  parentPath: boolean[];\n};\n\nconst TreeNodeContext = createContext<TreeNodeContextType | undefined>(\n  undefined\n);\n\nconst useTreeNode = () => {\n  const context = useContext(TreeNodeContext);\n  if (!context) {\n    throw new Error(\"TreeNode components must be used within a TreeNode\");\n  }\n  return context;\n};\n\nexport type TreeProviderProps = {\n  children: ReactNode;\n  defaultExpandedIds?: string[];\n  showLines?: boolean;\n  showIcons?: boolean;\n  selectable?: boolean;\n  multiSelect?: boolean;\n  selectedIds?: string[];\n  onSelectionChange?: (selectedIds: string[]) => void;\n  indent?: number;\n  animateExpand?: boolean;\n  className?: string;\n};\n\nexport const TreeProvider = ({\n  children,\n  defaultExpandedIds = [],\n  showLines = true,\n  showIcons = true,\n  selectable = true,\n  multiSelect = false,\n  selectedIds,\n  onSelectionChange,\n  indent = 20,\n  animateExpand = true,\n  className,\n}: TreeProviderProps) => {\n  const [expandedIds, setExpandedIds] = useState<Set<string>>(\n    new Set(defaultExpandedIds)\n  );\n  const [internalSelectedIds, setInternalSelectedIds] = useState<string[]>(\n    selectedIds ?? []\n  );\n\n  const isControlled =\n    selectedIds !== undefined && onSelectionChange !== undefined;\n  const currentSelectedIds = isControlled ? selectedIds : internalSelectedIds;\n\n  const toggleExpanded = useCallback((nodeId: string) => {\n    setExpandedIds((prev) => {\n      const newSet = new Set(prev);\n      if (newSet.has(nodeId)) {\n        newSet.delete(nodeId);\n      } else {\n        newSet.add(nodeId);\n      }\n      return newSet;\n    });\n  }, []);\n\n  const handleSelection = useCallback(\n    (nodeId: string, ctrlKey = false) => {\n      if (!selectable) {\n        return;\n      }\n\n      let newSelection: string[];\n\n      if (multiSelect && ctrlKey) {\n        newSelection = currentSelectedIds.includes(nodeId)\n          ? currentSelectedIds.filter((id) => id !== nodeId)\n          : [...currentSelectedIds, nodeId];\n      } else {\n        newSelection = currentSelectedIds.includes(nodeId) ? [] : [nodeId];\n      }\n\n      if (isControlled) {\n        onSelectionChange?.(newSelection);\n      } else {\n        setInternalSelectedIds(newSelection);\n      }\n    },\n    [\n      selectable,\n      multiSelect,\n      currentSelectedIds,\n      isControlled,\n      onSelectionChange,\n    ]\n  );\n\n  return (\n    <TreeContext.Provider\n      value={{\n        expandedIds,\n        selectedIds: currentSelectedIds,\n        toggleExpanded,\n        handleSelection,\n        showLines,\n        showIcons,\n        selectable,\n        multiSelect,\n        indent,\n        animateExpand,\n      }}\n    >\n      <motion.div\n        animate={{ opacity: 1, y: 0 }}\n        className={cn(\"w-full\", className)}\n        initial={{ opacity: 0, y: 10 }}\n        transition={{ duration: 0.3, ease: \"easeOut\" }}\n      >\n        {children}\n      </motion.div>\n    </TreeContext.Provider>\n  );\n};\n\nexport type TreeViewProps = HTMLAttributes<HTMLDivElement>;\n\nexport const TreeView = ({ className, children, ...props }: TreeViewProps) => (\n  <div className={cn(\"p-2\", className)} {...props}>\n    {children}\n  </div>\n);\n\nexport type TreeNodeProps = HTMLAttributes<HTMLDivElement> & {\n  nodeId?: string;\n  level?: number;\n  isLast?: boolean;\n  parentPath?: boolean[];\n  children?: ReactNode;\n};\n\nexport const TreeNode = ({\n  nodeId: providedNodeId,\n  level = 0,\n  isLast = false,\n  parentPath = [],\n  children,\n  className,\n  onClick,\n  ...props\n}: TreeNodeProps) => {\n  const generatedId = useId();\n  const nodeId = providedNodeId ?? generatedId;\n\n  // Build the parent path - mark positions where the parent was the last child\n  const currentPath = level === 0 ? [] : [...parentPath];\n  if (level > 0 && parentPath.length < level - 1) {\n    // Fill in missing levels with false (not last)\n    while (currentPath.length < level - 1) {\n      currentPath.push(false);\n    }\n  }\n  if (level > 0) {\n    currentPath[level - 1] = isLast;\n  }\n\n  return (\n    <TreeNodeContext.Provider\n      value={{\n        nodeId,\n        level,\n        isLast,\n        parentPath: currentPath,\n      }}\n    >\n      <div className={cn(\"select-none\", className)} {...props}>\n        {children}\n      </div>\n    </TreeNodeContext.Provider>\n  );\n};\n\nexport type TreeNodeTriggerProps = ComponentProps<typeof motion.div>;\n\nexport const TreeNodeTrigger = ({\n  children,\n  className,\n  onClick,\n  ...props\n}: TreeNodeTriggerProps) => {\n  const { selectedIds, toggleExpanded, handleSelection, indent } = useTree();\n  const { nodeId, level } = useTreeNode();\n  const isSelected = selectedIds.includes(nodeId);\n\n  return (\n    <motion.div\n      className={cn(\n        \"group relative mx-1 flex cursor-pointer items-center rounded-md px-3 py-2 transition-all duration-200\",\n        \"hover:bg-accent/50\",\n        isSelected && \"bg-accent/80\",\n        className\n      )}\n      onClick={(e) => {\n        toggleExpanded(nodeId);\n        handleSelection(nodeId, e.ctrlKey || e.metaKey);\n        onClick?.(e);\n      }}\n      style={{ paddingLeft: level * (indent ?? 0) + 8 }}\n      whileTap={{ scale: 0.98, transition: { duration: 0.1 } }}\n      {...props}\n    >\n      <TreeLines />\n      {children as ReactNode}\n    </motion.div>\n  );\n};\n\nexport const TreeLines = () => {\n  const { showLines, indent } = useTree();\n  const { level, isLast, parentPath } = useTreeNode();\n\n  if (!showLines || level === 0) {\n    return null;\n  }\n\n  return (\n    <div className=\"pointer-events-none absolute top-0 bottom-0 left-0\">\n      {/* Render vertical lines for all parent levels */}\n      {Array.from({ length: level }, (_, index) => {\n        const shouldHideLine = parentPath[index] === true;\n        if (shouldHideLine && index === level - 1) {\n          return null;\n        }\n\n        return (\n          <div\n            className=\"absolute top-0 bottom-0 border-border/40 border-l\"\n            key={index.toString()}\n            style={{\n              left: index * (indent ?? 0) + 12,\n              display: shouldHideLine ? \"none\" : \"block\",\n            }}\n          />\n        );\n      })}\n\n      {/* Horizontal connector line */}\n      <div\n        className=\"absolute top-1/2 border-border/40 border-t\"\n        style={{\n          left: (level - 1) * (indent ?? 0) + 12,\n          width: (indent ?? 0) - 4,\n          transform: \"translateY(-1px)\",\n        }}\n      />\n\n      {/* Vertical line to midpoint for last items */}\n      {isLast && (\n        <div\n          className=\"absolute top-0 border-border/40 border-l\"\n          style={{\n            left: (level - 1) * (indent ?? 0) + 12,\n            height: \"50%\",\n          }}\n        />\n      )}\n    </div>\n  );\n};\n\nexport type TreeNodeContentProps = ComponentProps<typeof motion.div> & {\n  hasChildren?: boolean;\n};\n\nexport const TreeNodeContent = ({\n  children,\n  hasChildren = false,\n  className,\n  ...props\n}: TreeNodeContentProps) => {\n  const { animateExpand, expandedIds } = useTree();\n  const { nodeId } = useTreeNode();\n  const isExpanded = expandedIds.has(nodeId);\n\n  return (\n    <AnimatePresence>\n      {hasChildren && isExpanded && (\n        <motion.div\n          animate={{ height: \"auto\", opacity: 1 }}\n          className=\"overflow-hidden\"\n          exit={{ height: 0, opacity: 0 }}\n          initial={{ height: 0, opacity: 0 }}\n          transition={{\n            duration: animateExpand ? 0.3 : 0,\n            ease: \"easeInOut\",\n          }}\n        >\n          <motion.div\n            animate={{ y: 0 }}\n            className={className}\n            exit={{ y: -10 }}\n            initial={{ y: -10 }}\n            transition={{\n              duration: animateExpand ? 0.2 : 0,\n              delay: animateExpand ? 0.1 : 0,\n            }}\n            {...props}\n          >\n            {children}\n          </motion.div>\n        </motion.div>\n      )}\n    </AnimatePresence>\n  );\n};\n\nexport type TreeExpanderProps = ComponentProps<typeof motion.div> & {\n  hasChildren?: boolean;\n};\n\nexport const TreeExpander = ({\n  hasChildren = false,\n  className,\n  onClick,\n  ...props\n}: TreeExpanderProps) => {\n  const { expandedIds, toggleExpanded } = useTree();\n  const { nodeId } = useTreeNode();\n  const isExpanded = expandedIds.has(nodeId);\n\n  if (!hasChildren) {\n    return <div className=\"mr-1 h-4 w-4\" />;\n  }\n\n  return (\n    <motion.div\n      animate={{ rotate: isExpanded ? 90 : 0 }}\n      className={cn(\n        \"mr-1 flex h-4 w-4 cursor-pointer items-center justify-center\",\n        className\n      )}\n      onClick={(e) => {\n        e.stopPropagation();\n        toggleExpanded(nodeId);\n        onClick?.(e);\n      }}\n      transition={{ duration: 0.2, ease: \"easeInOut\" }}\n      {...props}\n    >\n      <ChevronRight className=\"h-3 w-3 text-muted-foreground\" />\n    </motion.div>\n  );\n};\n\nexport type TreeIconProps = ComponentProps<typeof motion.div> & {\n  icon?: ReactNode;\n  hasChildren?: boolean;\n};\n\nexport const TreeIcon = ({\n  icon,\n  hasChildren = false,\n  className,\n  ...props\n}: TreeIconProps) => {\n  const { showIcons, expandedIds } = useTree();\n  const { nodeId } = useTreeNode();\n  const isExpanded = expandedIds.has(nodeId);\n\n  if (!showIcons) {\n    return null;\n  }\n\n  const getDefaultIcon = () =>\n    hasChildren ? (\n      isExpanded ? (\n        <FolderOpen className=\"h-4 w-4\" />\n      ) : (\n        <Folder className=\"h-4 w-4\" />\n      )\n    ) : (\n      <File className=\"h-4 w-4\" />\n    );\n\n  return (\n    <motion.div\n      className={cn(\n        \"mr-2 flex h-4 w-4 items-center justify-center text-muted-foreground\",\n        className\n      )}\n      transition={{ duration: 0.15 }}\n      whileHover={{ scale: 1.1 }}\n      {...props}\n    >\n      {icon || getDefaultIcon()}\n    </motion.div>\n  );\n};\n\nexport type TreeLabelProps = HTMLAttributes<HTMLSpanElement>;\n\nexport const TreeLabel = ({ className, ...props }: TreeLabelProps) => (\n  <span className={cn(\"font flex-1 truncate text-sm\", className)} {...props} />\n);\n","target":"components/kibo-ui/tree/index.tsx"}],"css":{}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"typography","type":"registry:style","title":"typography","description":"A typography component designed to display text with a consistent style.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":[],"devDependencies":[],"registryDependencies":[],"files":[],"css":{"@layer base":{".typography":{"max-width":"65ch","color":"var(--color-foreground)"},".typography h1:not(.not-typography h1)":{"scroll-margin":"calc(var(--spacing) * 20)","font-size":"var(--text-4xl)","line-height":"var(--text-4xl--line-height)","font-weight":"800","letter-spacing":"var(--tracking-tight)"},".typography h2:not(.not-typography h2)":{"scroll-margin":"calc(var(--spacing) * 20)","border-bottom":"1px solid var(--color-border)","padding-bottom":"calc(var(--spacing) * 2)","font-size":"var(--text-3xl)","line-height":"var(--text-3xl--line-height)","font-weight":"600","letter-spacing":"var(--tracking-tight)","margin-top":"calc(var(--spacing) * 12)"},".typography h2:not(.not-typography h2):first-child":{"margin-top":"0"},".typography h3:not(.not-typography h3)":{"scroll-margin":"calc(var(--spacing) * 20)","font-size":"var(--text-2xl)","line-height":"var(--text-2xl--line-height)","font-weight":"600","letter-spacing":"var(--tracking-tight)","margin-top":"calc(var(--spacing) * 12)"},".typography h3:not(.not-typography h3):first-child":{"margin-top":"0"},".typography h4:not(.not-typography h4)":{"scroll-margin":"calc(var(--spacing) * 20)","font-size":"var(--text-xl)","line-height":"var(--text-xl--line-height)","font-weight":"600","letter-spacing":"var(--tracking-tight)","margin-top":"calc(var(--spacing) * 12)"},".typography h4:not(.not-typography h4):first-child":{"margin-top":"0"},".typography h5:not(.not-typography h5)":{"scroll-margin":"calc(var(--spacing) * 20)","font-size":"var(--text-lg)","line-height":"var(--text-lg--line-height)","font-weight":"600","letter-spacing":"var(--tracking-tight)","margin-top":"calc(var(--spacing) * 12)"},".typography h5:not(.not-typography h5):first-child":{"margin-top":"0"},".typography h6:not(.not-typography h6)":{"scroll-margin":"calc(var(--spacing) * 20)","font-size":"var(--text-base)","line-height":"var(--text-base--line-height)","font-weight":"600","letter-spacing":"var(--tracking-tight)","margin-top":"calc(var(--spacing) * 12)"},".typography h6:not(.not-typography h6):first-child":{"margin-top":"0"},".typography h1:not(.not-typography h1) + p, .typography h2:not(.not-typography h2) + p, .typography h3:not(.not-typography h3) + p, .typography h4:not(.not-typography h4) + p, .typography h5:not(.not-typography h5) + p, .typography h6:not(.not-typography h6) + p":{"margin-top":"0"},".typography p:not(.not-typography p)":{"line-height":"1.75"},".typography p:not(.not-typography p):not(:first-child)":{"margin-top":"calc(var(--spacing) * 6)"},".typography li:not(.not-typography li)":{"line-height":"1.75"},".typography a:not(.not-typography a)":{"font-weight":"500","color":"var(--color-primary)","text-decoration":"underline","text-underline-offset":"var(--spacing)"},".typography blockquote:not(.not-typography blockquote)":{"margin-top":"calc(var(--spacing) * 6)","border-left":"2px solid var(--color-border)","padding-left":"calc(var(--spacing) * 6)","font-style":"italic"},".typography table:not(.not-typography table)":{"margin-top":"calc(var(--spacing) * 6)","margin-bottom":"calc(var(--spacing) * 6)","width":"100%","overflow-y":"auto"},".typography table:not(.not-typography table) thead tr":{"margin":"0","border-top":"1px solid var(--color-border)","padding":"0"},".typography table:not(.not-typography table) thead tr:nth-child(even)":{"background-color":"var(--color-muted)"},".typography table:not(.not-typography table) th":{"border":"1px solid var(--color-border)","padding":"calc(var(--spacing) * 2) calc(var(--spacing) * 4)","text-align":"left","font-weight":"700"},".typography table:not(.not-typography table) th[align=\"center\"]":{"text-align":"center"},".typography table:not(.not-typography table) th[align=\"right\"]":{"text-align":"right"},".typography table:not(.not-typography table) tbody tr":{"margin":"0","border-top":"1px solid var(--color-border)","padding":"0"},".typography table:not(.not-typography table) tbody tr:nth-child(even)":{"background-color":"var(--color-muted)"},".typography table:not(.not-typography table) td":{"border":"1px solid var(--color-border)","padding":"calc(var(--spacing) * 2) calc(var(--spacing) * 4)","text-align":"left"},".typography table:not(.not-typography table) td[align=\"center\"]":{"text-align":"center"},".typography table:not(.not-typography table) td[align=\"right\"]":{"text-align":"right"},".typography ul:not(.not-typography ul)":{"margin-top":"calc(var(--spacing) * 6)","margin-left":"calc(var(--spacing) * 6)","list-style":"disc"},".typography ul:not(.not-typography ul) > li":{"margin-top":"calc(var(--spacing) * 2)"},".typography ul:not(.not-typography ul) p":{"margin-top":"0","margin-bottom":"0","display":"inline"},".typography ol:not(.not-typography ol)":{"margin-top":"calc(var(--spacing) * 6)","margin-left":"calc(var(--spacing) * 6)","list-style":"decimal"},".typography ol:not(.not-typography ol) > li":{"margin-top":"calc(var(--spacing) * 2)"},".typography ol:not(.not-typography ol) p":{"margin-top":"0","margin-bottom":"0","display":"inline"},".typography pre:not(.not-typography pre)":{"background-color":"var(--color-muted)","border-radius":"calc(var(--radius) - 2px)","padding":"calc(var(--spacing) * 4)","margin-top":"calc(var(--spacing) * 6)","margin-bottom":"calc(var(--spacing) * 6)","font-size":"var(--text-sm)","line-height":"var(--text-sm--line-height)","overflow-y":"auto"},".typography code:not(pre code):not(.not-typography code)":{"position":"relative","border-radius":"var(--radius)","background-color":"var(--color-muted)","padding":"calc(var(--spacing) * 0.2) calc(var(--spacing) * 0.3)","font-family":"var(--font-mono)","font-size":"var(--text-sm)","line-height":"var(--text-sm--line-height)","font-weight":"600"},".typography .lead:not(.not-typography .lead)":{"font-size":"var(--text-xl)","line-height":"var(--text-xl--line-height)","color":"var(--color-muted-foreground)"},".typography .large:not(.not-typography .large)":{"font-size":"var(--text-lg)","line-height":"var(--text-lg--line-height)","font-weight":"600"},".typography .small:not(.not-typography .small)":{"font-size":"var(--text-sm)","line-height":"1","font-weight":"500"},".typography .muted:not(.not-typography .muted)":{"font-size":"var(--text-sm)","line-height":"var(--text-sm--line-height)","color":"var(--color-muted-foreground)"},".typography img:not(.not-typography img),\n    .typography picture:not(.not-typography picture),\n    .typography video:not(.not-typography video)":{"margin-top":"calc(var(--spacing) * 6)","margin-bottom":"calc(var(--spacing) * 6)"},".typography picture > img:not(.not-typography picture > img)":{"margin-top":"0","margin-bottom":"0"},".typography kbd:not(.not-typography kbd)":{"border-radius":"calc(var(--radius) - 2px)","background-color":"var(--color-muted)","padding":"calc(var(--spacing) * 0.5) calc(var(--spacing) * 1.5)","font-size":"var(--text-xs)","line-height":"var(--text-xs--line-height)","font-weight":"600"},".typography hr":{"margin-top":"calc(var(--spacing) * 10)","margin-bottom":"calc(var(--spacing) * 10)"},".typography dl:not(.not-typography dl)":{"margin-top":"calc(var(--spacing) * 6)","margin-bottom":"calc(var(--spacing) * 6)"},".typography dl:not(.not-typography dl) dt":{"font-weight":"600","margin-top":"calc(var(--spacing) * 6)","letter-spacing":"var(--tracking-tight)"},".typography dl:not(.not-typography dl) dt:first-child":{"margin-top":"0"},".typography details:not(.not-typography details)":{"margin-top":"calc(var(--spacing) * 6)"},".typography details:not(.not-typography details) summary":{"cursor":"pointer","font-weight":"600","margin-top":"calc(var(--spacing) * 6)","letter-spacing":"var(--tracking-tight)"},".typography details:not(.not-typography details) p:first-of-type":{"margin-top":"calc(var(--spacing) * 2)"},".typography mark:not(.not-typography mark)":{"background-color":"var(--color-yellow-300)"},".typography small:not(.not-typography small)":{"font-size":"var(--text-xs)","line-height":"1"},"@media (min-width: 1024px)":{".typography h1:not(.not-typography h1)":{"font-size":"var(--text-5xl)","line-height":"var(--text-5xl--line-height)"}}}}},{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"video-player","type":"registry:ui","title":"video-player","description":"A composable, shadcn/ui styled video player component that uses the media-chrome library.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["media-chrome"],"devDependencies":[],"registryDependencies":[],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport {\n  MediaControlBar,\n  MediaController,\n  MediaMuteButton,\n  MediaPlayButton,\n  MediaSeekBackwardButton,\n  MediaSeekForwardButton,\n  MediaTimeDisplay,\n  MediaTimeRange,\n  MediaVolumeRange,\n} from \"media-chrome/react\";\nimport type { ComponentProps, CSSProperties } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type VideoPlayerProps = ComponentProps<typeof MediaController>;\n\nconst variables = {\n  \"--media-primary-color\": \"var(--primary)\",\n  \"--media-secondary-color\": \"var(--background)\",\n  \"--media-text-color\": \"var(--foreground)\",\n  \"--media-background-color\": \"var(--background)\",\n  \"--media-control-hover-background\": \"var(--accent)\",\n  \"--media-font-family\": \"var(--font-sans)\",\n  \"--media-live-button-icon-color\": \"var(--muted-foreground)\",\n  \"--media-live-button-indicator-color\": \"var(--destructive)\",\n  \"--media-range-track-background\": \"var(--border)\",\n} as CSSProperties;\n\nexport const VideoPlayer = ({ style, ...props }: VideoPlayerProps) => (\n  <MediaController\n    style={{\n      ...variables,\n      ...style,\n    }}\n    {...props}\n  />\n);\n\nexport type VideoPlayerControlBarProps = ComponentProps<typeof MediaControlBar>;\n\nexport const VideoPlayerControlBar = (props: VideoPlayerControlBarProps) => (\n  <MediaControlBar {...props} />\n);\n\nexport type VideoPlayerTimeRangeProps = ComponentProps<typeof MediaTimeRange>;\n\nexport const VideoPlayerTimeRange = ({\n  className,\n  ...props\n}: VideoPlayerTimeRangeProps) => (\n  <MediaTimeRange className={cn(\"p-2.5\", className)} {...props} />\n);\n\nexport type VideoPlayerTimeDisplayProps = ComponentProps<\n  typeof MediaTimeDisplay\n>;\n\nexport const VideoPlayerTimeDisplay = ({\n  className,\n  ...props\n}: VideoPlayerTimeDisplayProps) => (\n  <MediaTimeDisplay className={cn(\"p-2.5\", className)} {...props} />\n);\n\nexport type VideoPlayerVolumeRangeProps = ComponentProps<\n  typeof MediaVolumeRange\n>;\n\nexport const VideoPlayerVolumeRange = ({\n  className,\n  ...props\n}: VideoPlayerVolumeRangeProps) => (\n  <MediaVolumeRange className={cn(\"p-2.5\", className)} {...props} />\n);\n\nexport type VideoPlayerPlayButtonProps = ComponentProps<typeof MediaPlayButton>;\n\nexport const VideoPlayerPlayButton = ({\n  className,\n  ...props\n}: VideoPlayerPlayButtonProps) => (\n  <MediaPlayButton className={cn(\"p-2.5\", className)} {...props} />\n);\n\nexport type VideoPlayerSeekBackwardButtonProps = ComponentProps<\n  typeof MediaSeekBackwardButton\n>;\n\nexport const VideoPlayerSeekBackwardButton = ({\n  className,\n  ...props\n}: VideoPlayerSeekBackwardButtonProps) => (\n  <MediaSeekBackwardButton className={cn(\"p-2.5\", className)} {...props} />\n);\n\nexport type VideoPlayerSeekForwardButtonProps = ComponentProps<\n  typeof MediaSeekForwardButton\n>;\n\nexport const VideoPlayerSeekForwardButton = ({\n  className,\n  ...props\n}: VideoPlayerSeekForwardButtonProps) => (\n  <MediaSeekForwardButton className={cn(\"p-2.5\", className)} {...props} />\n);\n\nexport type VideoPlayerMuteButtonProps = ComponentProps<typeof MediaMuteButton>;\n\nexport const VideoPlayerMuteButton = ({\n  className,\n  ...props\n}: VideoPlayerMuteButtonProps) => (\n  <MediaMuteButton className={cn(\"p-2.5\", className)} {...props} />\n);\n\nexport type VideoPlayerContentProps = ComponentProps<\"video\">;\n\nexport const VideoPlayerContent = ({\n  className,\n  ...props\n}: VideoPlayerContentProps) => (\n  <video className={cn(\"mt-0 mb-0\", className)} {...props} />\n);\n","target":"components/kibo-ui/video-player/index.tsx"}],"css":{}}]}