{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "primitives-animate-files",
  "title": "Files",
  "description": "Animated file-tree primitives with motion hover highlight, spring folder expand/collapse, and animated folder icon swap.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "@unlumen-ui/lib-get-strict-context"
  ],
  "files": [
    {
      "path": "registry/primitives/animate/files/index.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { motion, AnimatePresence } from \"motion/react\";\nimport { getStrictContext } from \"@/lib/get-strict-context\";\n\ntype FilesContextType = {\n  containerRef: React.RefObject<HTMLDivElement | null>;\n  highlightBounds: {\n    top: number;\n    left: number;\n    width: number;\n    height: number;\n  } | null;\n  setHighlightBounds: (\n    bounds: { top: number; left: number; width: number; height: number } | null,\n  ) => void;\n};\n\ntype FolderItemContextType = {\n  isOpen: boolean;\n  toggle: () => void;\n};\n\nconst [FilesProvider, useFiles] =\n  getStrictContext<FilesContextType>(\"FilesContext\");\nconst [FolderItemProvider, useFolderItem] =\n  getStrictContext<FolderItemContextType>(\"FolderItemContext\");\n\ntype FilesProps = React.ComponentProps<\"div\"> & {\n  defaultOpenFolders?: string[];\n};\n\nfunction Files({ children, className, ...props }: FilesProps) {\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const [highlightBounds, setHighlightBounds] = React.useState<{\n    top: number;\n    left: number;\n    width: number;\n    height: number;\n  } | null>(null);\n\n  return (\n    <FilesProvider\n      value={{ containerRef, highlightBounds, setHighlightBounds }}\n    >\n      <div\n        ref={containerRef}\n        className={className}\n        onMouseLeave={() => setHighlightBounds(null)}\n        {...props}\n      >\n        {children}\n      </div>\n    </FilesProvider>\n  );\n}\n\ntype FilesHighlightProps = React.ComponentProps<typeof motion.div>;\n\nfunction FilesHighlight({\n  className,\n  style,\n  children,\n  ...props\n}: FilesHighlightProps) {\n  const { highlightBounds } = useFiles();\n\n  return (\n    <>\n      <AnimatePresence>\n        {highlightBounds && (\n          <motion.div\n            layoutId=\"files-highlight\"\n            className={className}\n            initial={{ opacity: 0 }}\n            animate={{\n              opacity: 1,\n              top: highlightBounds.top,\n              left: highlightBounds.left,\n              width: highlightBounds.width,\n              height: highlightBounds.height,\n            }}\n            exit={{ opacity: 0 }}\n            transition={{ type: \"spring\", stiffness: 750, damping: 40 }}\n            style={{ position: \"absolute\", pointerEvents: \"none\", ...style }}\n            {...props}\n          />\n        )}\n      </AnimatePresence>\n      {children}\n    </>\n  );\n}\n\nfunction useHighlightHover() {\n  const { containerRef, setHighlightBounds } = useFiles();\n  const ref = React.useRef<HTMLDivElement>(null);\n\n  const onMouseEnter = React.useCallback(() => {\n    const el = ref.current;\n    const container = containerRef.current;\n    if (!el || !container) return;\n    const cRect = container.getBoundingClientRect();\n    const eRect = el.getBoundingClientRect();\n    setHighlightBounds({\n      top: eRect.top - cRect.top,\n      left: eRect.left - cRect.left,\n      width: eRect.width,\n      height: eRect.height,\n    });\n  }, [containerRef, setHighlightBounds]);\n\n  return { ref, onMouseEnter };\n}\n\ntype FolderItemProps = React.ComponentProps<\"div\"> & {\n  value: string;\n  defaultOpen?: boolean;\n};\n\nfunction FolderItem({\n  children,\n  defaultOpen = false,\n  ...props\n}: FolderItemProps) {\n  const [isOpen, setIsOpen] = React.useState(defaultOpen);\n  const toggle = React.useCallback(() => setIsOpen((v) => !v), []);\n\n  return (\n    <FolderItemProvider value={{ isOpen, toggle }}>\n      <div {...props}>{children}</div>\n    </FolderItemProvider>\n  );\n}\n\ntype FolderHeaderProps = React.ComponentProps<\"div\">;\n\nfunction FolderHeader({ children, ...props }: FolderHeaderProps) {\n  return <div {...props}>{children}</div>;\n}\n\ntype FolderTriggerProps = React.ComponentProps<\"button\">;\n\nfunction FolderTrigger({ children, onClick, ...props }: FolderTriggerProps) {\n  const { toggle } = useFolderItem();\n\n  return (\n    <button\n      type=\"button\"\n      onClick={(e) => {\n        toggle();\n        onClick?.(e);\n      }}\n      {...props}\n    >\n      {children}\n    </button>\n  );\n}\n\ntype FolderHighlightProps = React.ComponentProps<\"div\">;\n\nfunction FolderHighlight({ children, ...props }: FolderHighlightProps) {\n  const { ref, onMouseEnter } = useHighlightHover();\n\n  return (\n    <div ref={ref} onMouseEnter={onMouseEnter} {...props}>\n      {children}\n    </div>\n  );\n}\n\ntype FolderProps = React.ComponentProps<\"div\">;\n\nfunction Folder({ children, ...props }: FolderProps) {\n  return <div {...props}>{children}</div>;\n}\n\ntype FolderIconProps = {\n  openIcon: React.ReactNode;\n  closeIcon: React.ReactNode;\n};\n\nfunction FolderIcon({ openIcon, closeIcon }: FolderIconProps) {\n  const { isOpen } = useFolderItem();\n  return (\n    <span className=\"inline-flex shrink-0 relative size-[1.125rem]\">\n      <AnimatePresence initial={false} mode=\"popLayout\">\n        <motion.span\n          key={isOpen ? \"open\" : \"close\"}\n          className=\"inline-flex\"\n          initial={{ scale: 0.5, opacity: 0, rotate: -15 }}\n          animate={{ scale: 1, opacity: 1, rotate: 0 }}\n          exit={{ scale: 0.5, opacity: 0, rotate: 15 }}\n          transition={{\n            type: \"spring\",\n            stiffness: 500,\n            damping: 30,\n            mass: 0.8,\n          }}\n        >\n          {isOpen ? openIcon : closeIcon}\n        </motion.span>\n      </AnimatePresence>\n    </span>\n  );\n}\n\ntype FileLabelProps = React.ComponentProps<\"span\">;\n\nfunction FileLabel({ children, ...props }: FileLabelProps) {\n  return <span {...props}>{children}</span>;\n}\n\ntype FolderContentProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  children: React.ReactNode;\n};\n\nfunction FolderContent({ children, className }: FolderContentProps) {\n  const { isOpen } = useFolderItem();\n\n  return (\n    <AnimatePresence initial={false}>\n      {isOpen && (\n        <motion.div\n          initial={{ height: 0, opacity: 0 }}\n          animate={{ height: \"auto\", opacity: 1 }}\n          exit={{ height: 0, opacity: 0 }}\n          transition={{ type: \"spring\", stiffness: 750, damping: 40 }}\n          className={className}\n          style={{ overflow: \"hidden\" }}\n        >\n          {children}\n        </motion.div>\n      )}\n    </AnimatePresence>\n  );\n}\n\ntype FileHighlightProps = React.ComponentProps<\"div\">;\n\nfunction FileHighlight({ children, ...props }: FileHighlightProps) {\n  const { ref, onMouseEnter } = useHighlightHover();\n\n  return (\n    <div ref={ref} onMouseEnter={onMouseEnter} {...props}>\n      {children}\n    </div>\n  );\n}\n\ntype FileProps = React.ComponentProps<\"div\">;\n\nfunction File({ children, ...props }: FileProps) {\n  return <div {...props}>{children}</div>;\n}\n\ntype FileIconProps = React.ComponentProps<\"span\">;\n\nfunction FileIcon({ children, ...props }: FileIconProps) {\n  return (\n    <span className=\"inline-flex shrink-0\" {...props}>\n      {children}\n    </span>\n  );\n}\n\nexport {\n  Files,\n  FilesHighlight,\n  FolderItem,\n  FolderHeader,\n  FolderTrigger,\n  FolderHighlight,\n  Folder,\n  FolderIcon,\n  FileLabel,\n  FolderContent,\n  FileHighlight,\n  File,\n  FileIcon,\n  type FilesProps,\n  type FilesHighlightProps,\n  type FolderItemProps,\n  type FolderHeaderProps,\n  type FolderTriggerProps,\n  type FolderHighlightProps,\n  type FolderProps,\n  type FolderIconProps,\n  type FileLabelProps,\n  type FolderContentProps,\n  type FileHighlightProps,\n  type FileProps,\n  type FileIconProps,\n};\n",
      "type": "registry:ui",
      "target": "components/unlumen-ui/primitives/animate/files.tsx"
    }
  ],
  "type": "registry:ui"
}