{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "doodle-icon",
  "type": "registry:ui",
  "title": "Doodle Icon",
  "description": "DoodleIcon and BoilFilter: renders a hand-drawn icon, draws its strokes in, and keeps the line wiggling through one shared SVG filter.",
  "docs": "Mount <BoilFilter id=\"boil\" /> once per page, then pass boilId=\"boil\" to each icon. Add draw to trace it on.",
  "files": [
    {
      "path": "registry/ui/doodle-icon.tsx",
      "type": "registry:ui",
      "content": "import type { CSSProperties, SVGProps } from \"react\"\n\n/*\n * Doodle Icons for the shadcn CLI: the one shared piece every icon item\n * installs. It is ~/doodle-icons/src (DoodleIcon.tsx, BoilFilter.tsx,\n * boil.ts) folded into one file, minus the icon table: each icon arrives as\n * its own file and passes its strokes in. Keep the numbers in step with\n * boil.ts — the site, the npm package and this file should boil alike.\n *\n * scripts/build-shadcn-registry.mjs publishes this file as\n * public/r/doodle-icon.json, so it is typechecked by `next build` but never\n * imported by the site.\n */\n\nexport type DoodleIconData = {\n  /** kebab-case, stable across releases */\n  name: string\n  /** SVG path `d` strings, drawn in order */\n  paths: string[]\n}\n\n/** The values doodle.oreoui.com runs at. */\nconst BOIL = {\n  /** how far the ink wanders, in viewBox units: 1 is a shiver, 8 is a mess */\n  amplitude: 4,\n  duration: \"0.86s\",\n  frequency: 0.055,\n  octaves: 2,\n  frames: 6,\n} as const\n\nconst STROKE_WIDTH = 3.4\n\nexport type DoodleIconProps = Omit<SVGProps<SVGSVGElement>, \"name\"> & {\n  icon: DoodleIconData\n  /** px, applied to both axes */\n  size?: number | string\n  strokeWidth?: number\n  /** id of a mounted <BoilFilter>; omit to render the icon still */\n  boilId?: string\n  /** draw the strokes on, one after another, the first time it renders */\n  draw?: boolean\n  /** seconds per stroke */\n  drawDuration?: number\n  /** seconds before the first stroke starts */\n  drawDelay?: number\n}\n\n/**\n * One doodle icon. Strokes inherit `currentColor`, so colour comes from CSS.\n *\n *   <BoilFilter id=\"boil\" />\n *   <HeartDoodle boilId=\"boil\" draw />\n */\nexport function DoodleIcon({\n  icon,\n  size = 24,\n  strokeWidth = STROKE_WIDTH,\n  boilId,\n  draw = false,\n  drawDuration = 0.32,\n  drawDelay = 0,\n  ...rest\n}: DoodleIconProps) {\n  // strokes overlap slightly, so a multi-stroke icon reads as one gesture\n  const step = drawDuration * 0.65\n\n  const drawStyle = (i: number): CSSProperties | undefined =>\n    draw\n      ? {\n          // Drawn is the resting state; the keyframes hide the ink and put it\n          // back, so an icon whose animation never runs is simply there.\n          animationName: \"doodle-draw\",\n          animationDuration: `${drawDuration}s`,\n          animationDelay: `${(drawDelay + i * step).toFixed(3)}s`,\n          animationTimingFunction: \"cubic-bezier(.65,.05,.36,1)\",\n          animationFillMode: \"backwards\",\n        }\n      : undefined\n\n  return (\n    <svg\n      viewBox=\"0 0 48 48\"\n      width={size}\n      height={size}\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={strokeWidth}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      role=\"img\"\n      aria-label={icon.name}\n      {...rest}\n    >\n      <g filter={boilId ? `url(#${boilId})` : undefined}>\n        {/* invisible spacer: holds the group bbox at 48x48 so the boil filter\n            region never collapses on flat icons like minus or ellipsis */}\n        <rect width=\"48\" height=\"48\" fill=\"none\" stroke=\"none\" />\n        {icon.paths.map((d, i) => (\n          <path key={i} d={d} pathLength={draw ? 1 : undefined} style={drawStyle(i)} />\n        ))}\n      </g>\n    </svg>\n  )\n}\n\n/**\n * Keyframes for the `draw` entrance. <BoilFilter> already renders it —\n * mount this on its own only if you want the entrance without the wiggle.\n */\nexport function DoodleStyles() {\n  return (\n    <style\n      dangerouslySetInnerHTML={{\n        __html:\n          \"@keyframes doodle-draw{\" +\n          \"from{stroke-dasharray:1;stroke-dashoffset:1;visibility:hidden}\" +\n          \"1%{visibility:visible}\" +\n          \"to{stroke-dasharray:1;stroke-dashoffset:0}}\" +\n          \"@media (prefers-reduced-motion:reduce){\" +\n          '[style*=\"doodle-draw\"]{animation:none!important}}',\n      }}\n    />\n  )\n}\n\n/**\n * The boil — one SVG filter shared by every icon on the page. Mount it once,\n * anywhere; icons reference it by id. Mount it again with another id for a\n * second intensity.\n */\nexport function BoilFilter({\n  id = \"doodle-boil\",\n  amplitude = BOIL.amplitude,\n  /** one full cycle of the wobble — lower is more frantic */\n  duration = BOIL.duration,\n  /** grain of the noise; higher is a finer, more jittery hand */\n  frequency = BOIL.frequency,\n  /** how many hand-drawn frames the loop pretends to have */\n  frames = BOIL.frames,\n}: {\n  id?: string\n  amplitude?: number\n  duration?: string\n  frequency?: number\n  frames?: number\n}) {\n  const seeds = Array.from({ length: frames }, (_, i) => i + 1).join(\";\")\n\n  return (\n    <>\n      <DoodleStyles />\n      <svg\n        width=\"0\"\n        height=\"0\"\n        aria-hidden=\"true\"\n        style={{ position: \"absolute\", pointerEvents: \"none\" }}\n      >\n        <filter id={id} x=\"-30%\" y=\"-30%\" width=\"160%\" height=\"160%\">\n          <feTurbulence\n            type=\"fractalNoise\"\n            baseFrequency={frequency}\n            numOctaves={BOIL.octaves}\n            seed=\"1\"\n            result=\"noise\"\n          >\n            {/* discrete steps: the ink jumps between drawings the way\n                hand-inked animation does, rather than sliding */}\n            <animate\n              attributeName=\"seed\"\n              values={seeds}\n              dur={duration}\n              repeatCount=\"indefinite\"\n              calcMode=\"discrete\"\n            />\n          </feTurbulence>\n          <feDisplacementMap\n            in=\"SourceGraphic\"\n            in2=\"noise\"\n            scale={amplitude}\n            xChannelSelector=\"R\"\n            yChannelSelector=\"G\"\n          />\n        </filter>\n      </svg>\n    </>\n  )\n}\n"
    }
  ]
}
