{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "printer",
  "title": "printer",
  "description": "Animated printer icon",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "icons/printer.tsx",
      "content": "'use client';\n\nimport type { Variants } from 'motion/react';\nimport { motion, useAnimation } from 'motion/react';\nimport type { HTMLAttributes } from 'react';\nimport { forwardRef, useId } from 'react';\nimport { useIconAnimation } from '@/lib/use-icon-animation';\nimport { cn } from '@/lib/utils';\n\nexport interface PrinterIconHandle {\n  startAnimation: () => void;\n  stopAnimation: () => void;\n}\n\ninterface PrinterIconProps extends HTMLAttributes<HTMLDivElement> {\n  size?: number;\n}\n\nconst PRINTER_PAGE =\n  'M17 14V19C17 20.4142 17 21.1213 16.5607 21.5607C16.1213 22 15.4142 22 14 22H10C8.58579 22 7.87868 22 7.43934 21.5607C7 21.1213 7 20.4142 7 19V14H17Z';\nconst PRINTER_INPUT_PAGE =\n  'M17 7V5C17 3.58579 17 2.87868 16.5607 2.43934C16.1213 2 15.4142 2 14 2H10C8.58579 2 7.87868 2 7.43934 2.43934C7 2.87868 7 3.58579 7 5V7';\nconst PRINTER_OUTPUT_PAGE_ROUNDED =\n  'M16.25 14H7.75C7.33579 14 7 14.3358 7 14.75V21.25C7 21.6642 7.33579 22 7.75 22H16.25C16.6642 22 17 21.6642 17 21.25V14.75C17 14.3358 16.6642 14 16.25 14Z';\n\nconst PRINT_DURATION = 0.92;\nconst PRINT_TIMES = [\n  0, 0.08, 0.16, 0.26, 0.38, 0.5, 0.62, 0.72, 0.82, 0.9, 0.96, 1,\n];\n\nconst printerRecoilVariants: Variants = {\n  normal: { transform: 'translateY(0px) scaleX(1) scaleY(1)' },\n  animate: {\n    transform: [\n      'translateY(0px) scaleX(1) scaleY(1)',\n      'translateY(0px) scaleX(1) scaleY(1)',\n      'translateY(0.16px) scaleX(1.005) scaleY(0.993)',\n      'translateY(0.38px) scaleX(1.012) scaleY(0.98)',\n      'translateY(0.7px) scaleX(1.022) scaleY(0.965)',\n      'translateY(0.32px) scaleX(1.012) scaleY(0.984)',\n      'translateY(-0.4px) scaleX(0.994) scaleY(1.012)',\n      'translateY(0.15px) scaleX(1.006) scaleY(0.991)',\n      'translateY(0px) scaleX(1) scaleY(1)',\n      'translateY(0px) scaleX(1) scaleY(1)',\n      'translateY(0px) scaleX(1) scaleY(1)',\n      'translateY(0px) scaleX(1) scaleY(1)',\n    ],\n    transition: {\n      duration: PRINT_DURATION,\n      ease: 'linear',\n      times: PRINT_TIMES,\n    },\n  },\n};\n\n// The official page hands off to a rounded sheet as the rollers release it.\nconst outgoingPageVariants: Variants = {\n  normal: {\n    d: PRINTER_PAGE,\n    transform: 'none',\n    visibility: 'visible',\n    transition: { duration: 0.18, ease: [0.23, 1, 0.32, 1] },\n  },\n  animate: {\n    transform: [\n      'translateY(0px)',\n      'translateY(0px)',\n      'translateY(0.7px)',\n      'translateY(2.2px)',\n      'translateY(4.8px)',\n      'translateY(8.2px)',\n      'translateY(12px)',\n      'translateY(15px)',\n      'translateY(17px)',\n      'translateY(17px)',\n      'translateY(0px)',\n      'translateY(0px)',\n    ],\n    visibility: [\n      'visible',\n      'hidden',\n      'hidden',\n      'hidden',\n      'hidden',\n      'hidden',\n      'hidden',\n      'hidden',\n      'hidden',\n      'hidden',\n      'hidden',\n      'visible',\n    ],\n    transition: {\n      duration: PRINT_DURATION,\n      ease: [0.77, 0, 0.175, 1],\n      times: PRINT_TIMES,\n    },\n  },\n};\n\nconst roundedOutgoingPageVariants: Variants = {\n  normal: { transform: 'none' },\n  animate: {\n    transform: [\n      'translateY(0px)',\n      'translateY(0px)',\n      'translateY(0.7px)',\n      'translateY(2.2px)',\n      'translateY(4.8px)',\n      'translateY(8.2px)',\n      'translateY(12px)',\n      'translateY(15px)',\n      'translateY(17px)',\n      'translateY(17px)',\n      'translateY(17px)',\n      'translateY(17px)',\n    ],\n    visibility: [\n      'visible',\n      'visible',\n      'visible',\n      'visible',\n      'visible',\n      'visible',\n      'visible',\n      'visible',\n      'hidden',\n      'hidden',\n      'hidden',\n      'hidden',\n    ],\n    transition: {\n      duration: PRINT_DURATION,\n      ease: [0.77, 0, 0.175, 1],\n      times: PRINT_TIMES,\n    },\n  },\n};\n\n// A replacement sheet uses the exact resting path so its final corner handoff\n// is invisible as it settles against the rollers.\nconst incomingPageVariants: Variants = {\n  normal: {\n    d: PRINTER_PAGE,\n    transform: 'translateY(-9px)',\n    transition: { duration: 0.18, ease: [0.23, 1, 0.32, 1] },\n  },\n  animate: {\n    transform: [\n      'translateY(-9px)',\n      'translateY(-9px)',\n      'translateY(-9px)',\n      'translateY(-9px)',\n      'translateY(-8.5px)',\n      'translateY(-7.2px)',\n      'translateY(-5px)',\n      'translateY(-2.5px)',\n      'translateY(-0.6px)',\n      'translateY(0.25px)',\n      'translateY(-0.1px)',\n      'translateY(0px)',\n    ],\n    transition: {\n      duration: PRINT_DURATION,\n      ease: [0.77, 0, 0.175, 1],\n      times: PRINT_TIMES,\n    },\n  },\n};\n\n// The input sheet feeds into the rollers while a replacement arrives from above.\nconst feedingInputPageVariants: Variants = {\n  normal: {\n    d: PRINTER_INPUT_PAGE,\n    transform: 'none',\n    visibility: 'visible',\n    transition: { duration: 0.18, ease: [0.23, 1, 0.32, 1] },\n  },\n  animate: {\n    transform: [\n      'translateY(0px)',\n      'translateY(0px)',\n      'translateY(0.6px)',\n      'translateY(2px)',\n      'translateY(4.5px)',\n      'translateY(7.5px)',\n      'translateY(9px)',\n      'translateY(9px)',\n      'translateY(9px)',\n      'translateY(9px)',\n      'translateY(0px)',\n      'translateY(0px)',\n    ],\n    visibility: [\n      'visible',\n      'visible',\n      'visible',\n      'visible',\n      'visible',\n      'visible',\n      'visible',\n      'visible',\n      'hidden',\n      'hidden',\n      'hidden',\n      'visible',\n    ],\n    transition: {\n      duration: PRINT_DURATION,\n      ease: [0.77, 0, 0.175, 1],\n      times: PRINT_TIMES,\n    },\n  },\n};\n\nconst replacementInputPageVariants: Variants = {\n  normal: {\n    d: PRINTER_INPUT_PAGE,\n    transform: 'translateY(-9px)',\n    transition: { duration: 0.18, ease: [0.23, 1, 0.32, 1] },\n  },\n  animate: {\n    transform: [\n      'translateY(-9px)',\n      'translateY(-9px)',\n      'translateY(-9px)',\n      'translateY(-9px)',\n      'translateY(-9px)',\n      'translateY(-8px)',\n      'translateY(-5.5px)',\n      'translateY(-2.5px)',\n      'translateY(-0.6px)',\n      'translateY(0.2px)',\n      'translateY(-0.08px)',\n      'translateY(0px)',\n    ],\n    transition: {\n      duration: PRINT_DURATION,\n      ease: [0.77, 0, 0.175, 1],\n      times: PRINT_TIMES,\n    },\n  },\n};\n\n// Close the printer base only while neither output sheet covers the gap.\nconst printerBaseVariants: Variants = {\n  normal: { visibility: 'hidden' },\n  animate: {\n    visibility: [\n      'hidden',\n      'hidden',\n      'hidden',\n      'hidden',\n      'visible',\n      'visible',\n      'visible',\n      'hidden',\n      'hidden',\n      'hidden',\n      'hidden',\n      'hidden',\n    ],\n    transition: {\n      duration: PRINT_DURATION,\n      ease: 'linear',\n      times: PRINT_TIMES,\n    },\n  },\n};\n\n// Generated sheets exist only during the feed cycle and hand back to source geometry.\nconst generatedGeometryVariants: Variants = {\n  normal: { visibility: 'hidden' },\n  animate: {\n    visibility: [\n      'hidden',\n      'visible',\n      'visible',\n      'visible',\n      'visible',\n      'visible',\n      'visible',\n      'visible',\n      'visible',\n      'visible',\n      'visible',\n      'hidden',\n    ],\n    transition: { duration: PRINT_DURATION, ease: 'linear', times: PRINT_TIMES },\n  },\n};\n\nconst PrinterIcon = forwardRef<PrinterIconHandle, PrinterIconProps>(\n  ({ onMouseEnter, onMouseLeave, className, size = 28, ...props }, ref) => {\n    const controls = useAnimation();\n    const outputClipId = useId();\n    const inputClipId = useId();\n    const { handleMouseEnter, handleMouseLeave } = useIconAnimation({\n      controls,\n      loops: false,\n      onMouseEnter,\n      onMouseLeave,\n      ref,\n    });\n\n    return (\n      <div\n        className={cn(className)}\n        onMouseEnter={handleMouseEnter}\n        onMouseLeave={handleMouseLeave}\n        {...props}\n      >\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width={size}\n          height={size}\n          viewBox=\"0 0 24 24\"\n          fill=\"none\"\n          overflow=\"hidden\"\n        >\n          <defs>\n            <clipPath id={outputClipId} clipPathUnits=\"userSpaceOnUse\">\n              <rect x=\"6\" y=\"13.9\" width=\"12\" height=\"10.1\" />\n            </clipPath>\n            <clipPath id={inputClipId} clipPathUnits=\"userSpaceOnUse\">\n              <rect x=\"6\" y=\"0\" width=\"12\" height=\"7.05\" />\n            </clipPath>\n          </defs>\n          <motion.g\n            variants={printerRecoilVariants}\n            animate={controls}\n            initial=\"normal\"\n            style={{ transformOrigin: '12px 12px' }}\n          >\n            <path\n              d=\"M17 17H18.6667C19.9128 17 20.5359 17 21 16.7321C21.304 16.5565 21.5565 16.304 21.732 16C22 15.5359 22 14.9128 22 13.6667C22 11.1744 22 9.9282 21.4641 9C21.113 8.39192 20.6081 7.88697 20 7.5359C19.0718 7 17.8256 7 15.3333 7H8.66667C6.17436 7 4.9282 7 4 7.5359C3.39192 7.88697 2.88697 8.39192 2.5359 9C2 9.9282 2 11.1744 2 13.6667C2 14.9128 2 15.5359 2.26795 16C2.44349 16.304 2.69596 16.5565 3 16.7321C3.4641 17 4.08718 17 5.33333 17H7\"\n              stroke=\"currentColor\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              strokeWidth=\"1.5\"\n            />\n            <g clipPath={`url(#${inputClipId})`}>\n              <motion.path\n                d={PRINTER_INPUT_PAGE}\n                stroke=\"currentColor\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                strokeWidth=\"1.5\"\n                variants={feedingInputPageVariants}\n                animate={controls}\n                initial=\"normal\"\n              />\n              <motion.g\n                variants={generatedGeometryVariants}\n                animate={controls}\n                initial=\"normal\"\n              >\n                <motion.path\n                  d={PRINTER_INPUT_PAGE}\n                  stroke=\"currentColor\"\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                  strokeWidth=\"1.5\"\n                  variants={replacementInputPageVariants}\n                  animate={controls}\n                  initial=\"normal\"\n                />\n              </motion.g>\n            </g>\n            <motion.g\n              variants={generatedGeometryVariants}\n              animate={controls}\n              initial=\"normal\"\n            >\n              <path\n                d=\"M7 14H17\"\n                stroke=\"currentColor\"\n                strokeLinecap=\"round\"\n                strokeWidth=\"1.5\"\n              />\n              <motion.path\n                d=\"M7 17H17\"\n                stroke=\"currentColor\"\n                strokeLinecap=\"round\"\n                strokeWidth=\"1.5\"\n                variants={printerBaseVariants}\n                animate={controls}\n                initial=\"normal\"\n              />\n              <motion.g clipPath={`url(#${outputClipId})`}>\n                <motion.path\n                  d={PRINTER_OUTPUT_PAGE_ROUNDED}\n                  stroke=\"currentColor\"\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                  strokeWidth=\"1.5\"\n                  variants={roundedOutgoingPageVariants}\n                  animate={controls}\n                  initial=\"normal\"\n                />\n                <motion.path\n                  d={PRINTER_PAGE}\n                  stroke=\"currentColor\"\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                  strokeWidth=\"1.5\"\n                  variants={incomingPageVariants}\n                  animate={controls}\n                  initial=\"normal\"\n                />\n              </motion.g>\n            </motion.g>\n            <motion.path\n              d={PRINTER_PAGE}\n              stroke=\"currentColor\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              strokeWidth=\"1.5\"\n              variants={outgoingPageVariants}\n              animate={controls}\n              initial=\"normal\"\n            />\n            <path\n              d=\"M18.8748 10.25H18.7498M18.9998 10.25C18.9998 10.3881 18.8879 10.5 18.7498 10.5C18.6117 10.5 18.4998 10.3881 18.4998 10.25C18.4998 10.1119 18.6117 10 18.7498 10C18.8879 10 18.9998 10.1119 18.9998 10.25Z\"\n              stroke=\"currentColor\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              strokeWidth=\"1.5\"\n            />\n          </motion.g>\n        </svg>\n      </div>\n    );\n  }\n);\n\nPrinterIcon.displayName = 'PrinterIcon';\n\nexport { PrinterIcon };\n",
      "type": "registry:ui"
    },
    {
      "path": "lib/use-icon-animation.ts",
      "content": "'use client';\n\nimport type { LegacyAnimationControls } from 'motion/react';\nimport { useReducedMotion } from 'motion/react';\nimport type {\n  ForwardedRef,\n  MouseEventHandler,\n} from 'react';\nimport { useCallback, useImperativeHandle, useRef } from 'react';\n\nexport interface AnimatedIconHandle {\n  startAnimation: () => void;\n  stopAnimation: () => void;\n}\n\ninterface UseIconAnimationOptions {\n  controls: LegacyAnimationControls;\n  loops?: boolean;\n  onMouseEnter?: MouseEventHandler<HTMLDivElement>;\n  onMouseLeave?: MouseEventHandler<HTMLDivElement>;\n  ref: ForwardedRef<AnimatedIconHandle>;\n}\n\nexport function useIconAnimation({\n  controls,\n  loops = false,\n  onMouseEnter,\n  onMouseLeave,\n  ref,\n}: UseIconAnimationOptions) {\n  const shouldReduceMotion = useReducedMotion();\n  const isControlledRef = useRef(false);\n  const isPlayingRef = useRef(false);\n  const runRef = useRef(0);\n\n  const startAnimation = useCallback(() => {\n    if (shouldReduceMotion || isPlayingRef.current) return;\n\n    isPlayingRef.current = true;\n    const run = ++runRef.current;\n    controls.set('normal');\n    void controls.start('animate').then(() => {\n      if (runRef.current === run) isPlayingRef.current = false;\n    });\n  }, [controls, shouldReduceMotion]);\n\n  const stopAnimation = useCallback(() => {\n    if (!loops) return;\n\n    runRef.current++;\n    isPlayingRef.current = false;\n    void controls.start('normal');\n  }, [controls, loops]);\n\n  useImperativeHandle(\n    ref,\n    () => {\n      isControlledRef.current = true;\n      return { startAnimation, stopAnimation };\n    },\n    [startAnimation, stopAnimation]\n  );\n\n  const handleMouseEnter = useCallback<MouseEventHandler<HTMLDivElement>>(\n    (event) => {\n      onMouseEnter?.(event);\n      if (!isControlledRef.current) startAnimation();\n    },\n    [onMouseEnter, startAnimation]\n  );\n\n  const handleMouseLeave = useCallback<MouseEventHandler<HTMLDivElement>>(\n    (event) => {\n      onMouseLeave?.(event);\n      if (!isControlledRef.current) stopAnimation();\n    },\n    [onMouseLeave, stopAnimation]\n  );\n\n  return { handleMouseEnter, handleMouseLeave };\n}\n",
      "type": "registry:lib"
    }
  ],
  "type": "registry:ui"
}