{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "image-editor-recipes",
  "title": "Recipes",
  "description": "Save a look as JSON and re-apply it to any photo.",
  "dependencies": [
    "lucide-react",
    "photocn@^0.1.1"
  ],
  "registryDependencies": [
    "button",
    "tooltip"
  ],
  "files": [
    {
      "path": "src/registry/image-editor/recipes.tsx",
      "content": "\"use client\";\n\nimport { useRef, type ChangeEvent } from \"react\";\nimport { BookmarkPlusIcon, FolderOpenIcon } from \"lucide-react\";\nimport {\n  buildRecipe,\n  downloadRecipe,\n  parseRecipe,\n  type EditorParams,\n  type RecipeV1,\n} from \"photocn\";\nimport { useImageEditor } from \"photocn/react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface RecipeActionsProps {\n  params: EditorParams;\n  onLoad: (recipe: RecipeV1) => void;\n  filename?: string;\n  disabled?: boolean;\n  className?: string;\n  /** Called when a picked recipe file can't be parsed. */\n  onError?: (error: unknown) => void;\n}\n\nexport function RecipeActions({\n  params,\n  onLoad,\n  filename,\n  disabled = false,\n  className,\n  onError = (error) => console.error(\"[photocn] Failed to load recipe\", error),\n}: RecipeActionsProps) {\n  const fileInputRef = useRef<HTMLInputElement | null>(null);\n\n  // Computed inline each render — recipe-building is cheap and avoids stale state.\n  const recipe = buildRecipe(params);\n  const saveDisabled = disabled || recipe === null;\n\n  const handleSave = () => {\n    if (!recipe) return;\n    downloadRecipe(recipe, filename);\n  };\n\n  const handleLoadClick = () => {\n    fileInputRef.current?.click();\n  };\n\n  const handleFileChange = async (event: ChangeEvent<HTMLInputElement>) => {\n    const input = event.currentTarget;\n    const file = input.files?.[0];\n    // Reset before async work so picking the same file again still fires onChange.\n    input.value = \"\";\n    if (!file) return;\n\n    try {\n      const text = await file.text();\n      const parsed = parseRecipe(text);\n      onLoad(parsed);\n    } catch (err) {\n      onError(err);\n    }\n  };\n\n  return (\n    <TooltipProvider>\n      <div\n        className={cn(\"flex items-center gap-1\", className)}\n        data-slot=\"recipe-actions\"\n      >\n        <Tooltip>\n          <TooltipTrigger\n            render={\n              saveDisabled ? (\n                <span className=\"inline-flex\">\n                  <Button\n                    aria-label=\"Save recipe\"\n                    disabled\n                    size=\"icon-sm\"\n                    type=\"button\"\n                    variant=\"ghost\"\n                  >\n                    <BookmarkPlusIcon aria-hidden=\"true\" />\n                    <span className=\"sr-only\">Save recipe</span>\n                  </Button>\n                </span>\n              ) : (\n                <Button\n                  aria-label=\"Save recipe\"\n                  onClick={handleSave}\n                  size=\"icon-sm\"\n                  type=\"button\"\n                  variant=\"ghost\"\n                >\n                  <BookmarkPlusIcon aria-hidden=\"true\" />\n                  <span className=\"sr-only\">Save recipe</span>\n                </Button>\n              )\n            }\n          />\n          <TooltipContent side=\"bottom\">\n            {recipe === null ? \"Nothing to save yet\" : \"Save recipe\"}\n          </TooltipContent>\n        </Tooltip>\n\n        <Tooltip>\n          <TooltipTrigger\n            render={\n              <Button\n                aria-label=\"Load recipe\"\n                disabled={disabled}\n                onClick={handleLoadClick}\n                size=\"icon-sm\"\n                type=\"button\"\n                variant=\"ghost\"\n              >\n                <FolderOpenIcon aria-hidden=\"true\" />\n                <span className=\"sr-only\">Load recipe</span>\n              </Button>\n            }\n          />\n          <TooltipContent side=\"bottom\">Load recipe</TooltipContent>\n        </Tooltip>\n\n        <input\n          accept=\"application/json\"\n          aria-hidden=\"true\"\n          className=\"hidden\"\n          onChange={handleFileChange}\n          ref={fileInputRef}\n          tabIndex={-1}\n          type=\"file\"\n        />\n      </div>\n    </TooltipProvider>\n  );\n}\n\nexport type ImageEditorRecipesProps = Partial<RecipeActionsProps>;\n\n/**\n * Save the current look as a `.recipe.json` and re-apply it to any photo.\n * Wired to the nearest `<ImageEditorProvider>`.\n */\nexport function ImageEditorRecipes(props: ImageEditorRecipesProps) {\n  const editor = useImageEditor();\n  return (\n    <RecipeActions\n      disabled={editor.disabled}\n      filename={editor.filename}\n      onLoad={(recipe) => void editor.recipes.apply(recipe)}\n      params={editor.params}\n      {...props}\n    />\n  );\n}\n\n",
      "type": "registry:component",
      "target": "components/image-editor/recipes.tsx"
    }
  ],
  "categories": [
    "image-editor"
  ],
  "type": "registry:component"
}