{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"folder","type":"registry:ui","title":"Folder","description":"A folder button that opens on hover or click and fans out papers that lean toward the pointer.","dependencies":["motion"],"registryDependencies":["https://ui.mekaigames.com/r/theme.json"],"files":[{"path":"components/ui/folder.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { motion, useMotionValue, useReducedMotion, useSpring, useTransform, type MotionValue, type Transition } from \"motion/react\";\nimport { useState, type PointerEvent, type ReactNode } from \"react\";\n\ntype FolderProps = {\n  /** Folder color. The back panel and tab use a darker shade of it. */\n  color?: string;\n  /** Scale factor. 1 is 120 × 96 pixels. */\n  size?: number;\n  /** Up to three papers. Keep them non-interactive: they sit inside a button. */\n  items?: ReactNode[];\n  /** Open state, for controlled use. */\n  open?: boolean;\n  defaultOpen?: boolean;\n  /** Called when the folder is clicked, with the requested state. */\n  onOpenChange?: (open: boolean) => void;\n  /** Peek inside while the pointer is over the folder. */\n  openOnHover?: boolean;\n  /** Accessible name of the button. */\n  label?: string;\n  className?: string;\n};\n\nconst W = 120;\nconst H = 96;\n\n/** A folder that opens on hover or click, fanning its papers out; the papers lean toward the pointer. */\nexport function Folder({\n  color = \"#ff784f\",\n  size = 1,\n  items,\n  open: controlled,\n  defaultOpen = false,\n  onOpenChange,\n  openOnHover = true,\n  label = \"Folder\",\n  className = \"\",\n}: FolderProps) {\n  const reduce = useReducedMotion() ?? false;\n  const [pinned, setPinned] = useState(defaultOpen);\n  const [hovered, setHovered] = useState(false);\n  // After a click closes the folder, ignore hover until the pointer leaves.\n  const [quiet, setQuiet] = useState(false);\n  const open = controlled ?? (pinned || (openOnHover && hovered && !quiet));\n  const papers = (items ?? [null, null, null]).slice(0, 3);\n  const dark = `color-mix(in srgb, ${color} 72%, black)`;\n\n  const px = useMotionValue(0);\n  const py = useMotionValue(0);\n  const sx = useSpring(px, { stiffness: 220, damping: 18 });\n  const sy = useSpring(py, { stiffness: 220, damping: 18 });\n\n  function onClick() {\n    const next = !open;\n    onOpenChange?.(next);\n    if (controlled !== undefined) return;\n    setPinned(next);\n    if (!next) setQuiet(true);\n  }\n\n  function onPointerMove(e: PointerEvent<HTMLButtonElement>) {\n    if (reduce || e.pointerType !== \"mouse\") return;\n    const rect = e.currentTarget.getBoundingClientRect();\n    px.set(((e.clientX - (rect.left + rect.width / 2)) / (rect.width / 2)) * 7);\n    py.set(((e.clientY - (rect.top + rect.height / 2)) / (rect.height / 2)) * 5);\n  }\n\n  function onPointerLeave() {\n    setHovered(false);\n    setQuiet(false);\n    px.set(0);\n    py.set(0);\n  }\n\n  const spring: Transition = reduce ? { duration: 0 } : { type: \"spring\", stiffness: 320, damping: 22 };\n\n  return (\n    <button\n      type=\"button\"\n      aria-label={label}\n      aria-expanded={open}\n      onClick={onClick}\n      onPointerEnter={(e) => e.pointerType === \"mouse\" && setHovered(true)}\n      onPointerLeave={onPointerLeave}\n      onPointerMove={onPointerMove}\n      className={`relative inline-block shrink-0 rounded-2xl ${className}`}\n      style={{ width: W * size, height: H * size }}\n    >\n      <span\n        aria-hidden=\"true\"\n        className=\"absolute left-0 top-0 block\"\n        style={{ width: W, height: H, transform: `scale(${size})`, transformOrigin: \"0 0\", perspective: 600 }}\n      >\n        {/* Back panel and tab */}\n        <span className=\"absolute left-0 top-0 h-5 w-[46px] rounded-t-[10px]\" style={{ backgroundColor: dark }} />\n        <span className=\"absolute inset-x-0 bottom-0 top-3 rounded-[14px] rounded-tl-none\" style={{ backgroundColor: dark }} />\n\n        {/* Papers */}\n        {papers.map((content, i) => (\n          <Paper key={i} index={i} total={papers.length} open={open} spring={spring} sx={sx} sy={sy}>\n            {content}\n          </Paper>\n        ))}\n\n        {/* Front flap */}\n        <motion.span\n          className=\"absolute inset-x-0 bottom-0 block h-[78%] rounded-[14px]\"\n          style={{\n            zIndex: 10,\n            transformOrigin: \"50% 100%\",\n            backgroundColor: color,\n            backgroundImage: \"linear-gradient(to bottom, rgb(255 255 255 / 0.18), transparent 45%)\",\n            boxShadow: \"0 -1px 0 rgb(255 255 255 / 0.25) inset\",\n          }}\n          initial={false}\n          animate={open ? { rotateX: -42, skewX: -4 } : { rotateX: 0, skewX: 0 }}\n          transition={spring}\n        />\n      </span>\n    </button>\n  );\n}\n\nfunction Paper({\n  index,\n  total,\n  open,\n  spring,\n  sx,\n  sy,\n  children,\n}: {\n  index: number;\n  total: number;\n  open: boolean;\n  spring: Transition;\n  sx: MotionValue<number>;\n  sy: MotionValue<number>;\n  children: ReactNode;\n}) {\n  // Spread the papers around the middle: -1, 0, 1 for three.\n  const k = index - (total - 1) / 2;\n  const depth = 0.6 + index * 0.25;\n  const x = useTransform(sx, (v) => v * depth);\n  const y = useTransform(sy, (v) => v * depth);\n  return (\n    <motion.span\n      className=\"absolute bottom-2 left-1/2 block h-16 w-[84px] -translate-x-1/2\"\n      initial={false}\n      animate={open ? { x: k * 26, y: -30 + Math.abs(k) * 7, rotate: k * 11 } : { x: k * 2, y: 0, rotate: 0 }}\n      transition={{ ...spring, delay: open ? index * 0.04 : 0 }}\n      style={{ zIndex: index }}\n    >\n      <motion.span\n        className=\"grid size-full place-items-center overflow-hidden rounded-lg bg-bone p-1.5 text-[10px] font-semibold leading-tight text-ink-950 shadow-[0_6px_14px_-6px_rgb(0_0_0/0.6)]\"\n        style={{ x, y }}\n      >\n        {children ?? (\n          <span className=\"block w-full space-y-1\">\n            <span className=\"block h-1 w-3/4 rounded-full bg-ink-950/20\" />\n            <span className=\"block h-1 w-full rounded-full bg-ink-950/15\" />\n            <span className=\"block h-1 w-2/3 rounded-full bg-ink-950/15\" />\n          </span>\n        )}\n      </motion.span>\n    </motion.span>\n  );\n}\n"}]}