{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "code",
  "dependencies": [
    "class-variance-authority",
    "lucide-react",
    "motion",
    "next-themes",
    "shiki"
  ],
  "registryDependencies": [
    "button",
    "utils"
  ],
  "files": [
    {
      "path": "src/components/watermelon-ui/code.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport { useTheme } from 'next-themes';\n\n\nimport { cn } from '@/lib/utils';\nimport { useInView, type UseInViewOptions } from 'motion/react';\n\n\ntype CodeContextType = {\n  code: string;\n};\n\nconst [CodeProvider, useCode] =\n  getStrictContext<CodeContextType>('CodeContext');\n\ntype CodeProps = React.ComponentProps<'div'> & {\n  code: string;\n};\n\nfunction Code({ className, code, ...props }: CodeProps) {\n  return (\n    <CodeProvider value={{ code }}>\n      <div\n        className={cn(\n          'relative flex flex-col overflow-hidden border bg-accent/50 rounded-lg',\n          className,\n        )}\n        {...props}\n      />\n    </CodeProvider>\n  );\n}\n\ntype CodeHeaderProps = React.ComponentProps<'div'> & {\n  icon?: React.ElementType;\n  copyButton?: boolean;\n};\n\nfunction CodeHeader({\n  className,\n  children,\n  icon: Icon,\n  copyButton = false,\n  ...props\n}: CodeHeaderProps) {\n  const { code } = useCode();\n\n  return (\n    <div\n      className={cn(\n        'bg-accent shrink-0 gap-x-2 border-b border-border/75 dark:border-border/50 text-sm flex text-muted-foreground items-center px-4 w-full h-10',\n        className,\n      )}\n      {...props}\n    >\n      {Icon && <Icon className=\"size-4\" />}\n      {children}\n      {copyButton && (\n        <CopyButton\n          content={code}\n          size=\"xs\"\n          variant=\"ghost\"\n          className=\"ml-auto w-auto h-auto p-2 -mr-2\"\n        />\n      )}\n    </div>\n  );\n}\n\ntype CodeBlockProps = Omit<CodeBlockPropsPrimitive, 'code'> & {\n  cursor?: boolean;\n};\n\nfunction CodeBlock({ cursor, className, ...props }: CodeBlockProps) {\n  const { resolvedTheme } = useTheme();\n  const { code } = useCode();\n  const scrollRef = React.useRef<HTMLDivElement>(null);\n\n  return (\n    <CodeBlockPrimitive\n      ref={scrollRef}\n      theme={resolvedTheme === 'dark' ? 'dark' : 'light'}\n      scrollContainerRef={scrollRef}\n      className={cn(\n        'relative text-sm p-4 overflow-auto',\n        '[&>pre,_&_code]:!bg-transparent [&>pre,_&_code]:[background:transparent_!important] [&>pre,_&_code]:border-none [&_code]:!text-[13px] [&_code_.line]:!px-0',\n        cursor &&\n          \"data-[done=false]:[&_.line:last-of-type::after]:content-['|'] data-[done=false]:[&_.line:last-of-type::after]:inline-block data-[done=false]:[&_.line:last-of-type::after]:w-[1ch] data-[done=false]:[&_.line:last-of-type::after]:-translate-px\",\n        className,\n      )}\n      code={code}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Code,\n  CodeHeader,\n  CodeBlock,\n  type CodeProps,\n  type CodeHeaderProps,\n  type CodeBlockProps,\n};\n\n\ntype CodeBlockPropsPrimitive = React.ComponentProps<'div'> & {\n  code: string;\n  lang: string;\n  theme?: 'light' | 'dark';\n  themes?: { light: string; dark: string };\n  writing?: boolean;\n  duration?: number;\n  delay?: number;\n  onDone?: () => void;\n  onWrite?: (info: { index: number; length: number; done: boolean }) => void;\n  scrollContainerRef?: React.RefObject<HTMLElement | null>;\n} & UseIsInViewOptions;\n\nfunction CodeBlockPrimitive({\n  ref,\n  code,\n  lang,\n  theme = 'light',\n  themes = {\n    light: 'github-light',\n    dark: 'github-dark',\n  },\n  writing = false,\n  duration = 5000,\n  delay = 0,\n  onDone,\n  onWrite,\n  scrollContainerRef,\n  inView = false,\n  inViewOnce = true,\n  inViewMargin = '0px',\n  ...props\n}: CodeBlockPropsPrimitive) {\n  const { ref: localRef, isInView } = useIsInView(\n    ref as React.Ref<HTMLDivElement>,\n    {\n      inView,\n      inViewOnce,\n      inViewMargin,\n    },\n  );\n\n  const [visibleCode, setVisibleCode] = React.useState('');\n  const [highlightedCode, setHighlightedCode] = React.useState('');\n  const [isDone, setIsDone] = React.useState(false);\n\n  React.useEffect(() => {\n    if (!visibleCode.length || !isInView) return;\n\n    const loadHighlightedCode = async () => {\n      try {\n        const { codeToHtml } = await import('shiki');\n\n        const highlighted = await codeToHtml(visibleCode, {\n          lang,\n          themes,\n          defaultColor: theme,\n        });\n\n        setHighlightedCode(highlighted);\n      } catch (e) {\n        console.error(`Language \"${lang}\" could not be loaded.`, e);\n      }\n    };\n\n    loadHighlightedCode();\n  }, [lang, themes, writing, isInView, duration, delay, visibleCode, theme]);\n\n  React.useEffect(() => {\n    if (!writing) {\n      setVisibleCode(code);\n      onDone?.();\n      onWrite?.({ index: code.length, length: code.length, done: true });\n      return;\n    }\n\n    if (!code.length || !isInView) return;\n\n    const characters = Array.from(code);\n    let index = 0;\n    const totalDuration = duration;\n    const interval = totalDuration / characters.length;\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    let intervalId: any;\n\n    const timeout = setTimeout(() => {\n      intervalId = setInterval(() => {\n        if (index < characters.length) {\n          setVisibleCode(() => {\n            const nextChar = characters.slice(0, index + 1).join('');\n            onWrite?.({\n              index: index + 1,\n              length: characters.length,\n              done: false,\n            });\n            index += 1;\n            return nextChar;\n          });\n          localRef.current?.scrollTo({\n            top: localRef.current?.scrollHeight,\n            behavior: 'smooth',\n          });\n        } else {\n          clearInterval(intervalId);\n          setIsDone(true);\n          onDone?.();\n          onWrite?.({\n            index: characters.length,\n            length: characters.length,\n            done: true,\n          });\n        }\n      }, interval);\n    }, delay);\n\n    return () => {\n      clearTimeout(timeout);\n      clearInterval(intervalId);\n    };\n  }, [code, duration, delay, isInView, writing, onDone, onWrite, localRef]);\n\n  React.useEffect(() => {\n    if (!writing || !isInView) return;\n    const el =\n      scrollContainerRef?.current ??\n      (localRef.current?.parentElement as HTMLElement | null) ??\n      (localRef.current as unknown as HTMLElement | null);\n\n    if (!el) return;\n\n    requestAnimationFrame(() => {\n      el.scrollTo({\n        top: el.scrollHeight,\n        behavior: 'smooth',\n      });\n    });\n  }, [highlightedCode, writing, isInView, scrollContainerRef, localRef]);\n\n  return (\n    <div\n      ref={localRef}\n      data-slot=\"code-block\"\n      data-writing={writing}\n      data-done={isDone}\n      dangerouslySetInnerHTML={{ __html: highlightedCode }}\n      {...props}\n    />\n  );\n}\n\nexport { CodeBlockPrimitive, type CodeBlockPropsPrimitive };\n\n\n\ninterface UseIsInViewOptions {\n  inView?: boolean;\n  inViewOnce?: boolean;\n  inViewMargin?: UseInViewOptions['margin'];\n}\n\nfunction useIsInView<T extends HTMLElement = HTMLElement>(\n  ref: React.Ref<T>,\n  options: UseIsInViewOptions = {},\n) {\n  const { inView, inViewOnce = false, inViewMargin = '0px' } = options;\n  const localRef = React.useRef<T>(null);\n  React.useImperativeHandle(ref, () => localRef.current as T);\n  const inViewResult = useInView(localRef, {\n    once: inViewOnce,\n    margin: inViewMargin,\n  });\n  const isInView = !inView || inViewResult;\n  return { ref: localRef, isInView };\n}\n\nexport { useIsInView, type UseIsInViewOptions };\n\n\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { AnimatePresence, motion } from 'motion/react';\nimport { CheckIcon, CopyIcon } from 'lucide-react';\nimport { Button } from './button';\n\n\nconst buttonVariants = cva(\n  \"flex items-center justify-center rounded-md transition-[box-shadow,_color,_background-color,_border-color,_outline-color,_text-decoration-color,_fill,_stroke] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n  {\n    variants: {\n      variant: {\n        default:\n          'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',\n        accent: 'bg-accent text-accent-foreground shadow-xs hover:bg-accent/90',\n        destructive:\n          'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',\n        outline:\n          'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',\n        ghost:\n          'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default: 'size-9',\n        xs: \"size-7 [&_svg:not([class*='size-'])]:size-3.5 rounded-md\",\n        sm: 'size-8 rounded-md',\n        lg: 'size-10 rounded-md',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  },\n);\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype CopyButtonProps = Omit<any, 'children'> &\n  VariantProps<typeof buttonVariants> & {\n    content: string;\n    copied?: boolean;\n    onCopiedChange?: (copied: boolean, content?: string) => void;\n    delay?: number;\n  };\n\nfunction CopyButton({\n  className,\n  content,\n  copied,\n  onCopiedChange,\n  onClick,\n  variant,\n  size,\n  delay = 3000,\n  ...props\n}: CopyButtonProps) {\n  const [isCopied, setIsCopied] = useControlledState({\n    value: copied,\n    onChange: onCopiedChange,\n  });\n\n  const handleCopy = React.useCallback(\n    (e: React.MouseEvent<HTMLButtonElement>) => {\n      onClick?.(e);\n      if (copied) return;\n      if (content) {\n        navigator.clipboard\n          .writeText(content)\n          .then(() => {\n            setIsCopied(true);\n            onCopiedChange?.(true, content);\n            setTimeout(() => {\n              setIsCopied(false);\n              onCopiedChange?.(false);\n            }, delay);\n          })\n          .catch((error) => {\n            console.error('Error copying command', error);\n          });\n      }\n    },\n    [onClick, copied, content, setIsCopied, onCopiedChange, delay],\n  );\n\n  const Icon = isCopied ? CheckIcon : CopyIcon;\n\n  return (\n    <Button\n      data-slot=\"copy-button\"\n      className={cn(buttonVariants({ variant, size, className }))}\n      onClick={handleCopy}\n      {...props}\n    >\n      <AnimatePresence mode=\"popLayout\">\n        <motion.span\n          key={isCopied ? 'check' : 'copy'}\n          data-slot=\"copy-button-icon\"\n          initial={{ scale: 0, opacity: 0.4, filter: 'blur(4px)' }}\n          animate={{ scale: 1, opacity: 1, filter: 'blur(0px)' }}\n          exit={{ scale: 0, opacity: 0.4, filter: 'blur(4px)' }}\n          transition={{ duration: 0.25 }}\n        >\n          <Icon />\n        </motion.span>\n      </AnimatePresence>\n    </Button>\n  );\n}\n\nexport { CopyButton, buttonVariants, type CopyButtonProps };\n\n\ninterface CommonControlledStateProps<T> {\n  value?: T;\n  defaultValue?: T;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useControlledState<T, Rest extends any[] = []>(\n  props: CommonControlledStateProps<T> & {\n    onChange?: (value: T, ...args: Rest) => void;\n  },\n): readonly [T, (next: T, ...args: Rest) => void] {\n  const { value, defaultValue, onChange } = props;\n\n  const [state, setInternalState] = React.useState<T>(\n    value !== undefined ? value : (defaultValue as T),\n  );\n\n  React.useEffect(() => {\n    if (value !== undefined) setInternalState(value);\n  }, [value]);\n\n  const setState = React.useCallback(\n    (next: T, ...args: Rest) => {\n      setInternalState(next);\n      onChange?.(next, ...args);\n    },\n    [onChange],\n  );\n\n  return [state, setState] as const;\n}\n\nfunction getStrictContext<T>(\n  name?: string,\n): readonly [\n  ({\n    value,\n    children,\n  }: {\n    value: T;\n    children?: React.ReactNode;\n  }) => React.JSX.Element,\n  () => T,\n] {\n  const Context = React.createContext<T | undefined>(undefined);\n\n  const Provider = ({\n    value,\n    children,\n  }: {\n    value: T;\n    children?: React.ReactNode;\n  }) => <Context.Provider value={value}>{children}</Context.Provider>;\n\n  const useSafeContext = () => {\n    const ctx = React.useContext(Context);\n    if (ctx === undefined) {\n      throw new Error(`useContext must be used within ${name ?? 'a Provider'}`);\n    }\n    return ctx;\n  };\n\n  return [Provider, useSafeContext] as const;\n}\n\nexport { getStrictContext };\n",
      "type": "registry:ui",
      "target": "components/ui/code.tsx"
    }
  ],
  "type": "registry:ui"
}
