{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "image-editor-toolbar",
  "title": "Toolbar",
  "description": "Tool switcher plus undo, redo, open, reset and compare buttons.",
  "dependencies": [
    "lucide-react",
    "photocn@^0.1.1"
  ],
  "registryDependencies": [
    "button",
    "toggle-group",
    "tooltip"
  ],
  "files": [
    {
      "path": "src/registry/image-editor/toolbar.tsx",
      "content": "\"use client\";\n\nimport type { ComponentProps, ReactNode } from \"react\";\nimport {\n  ActivityIcon,\n  EyeIcon,\n  ImagePlusIcon,\n  RotateCcwIcon,\n  CropIcon,\n  DropletsIcon,\n  InfoIcon,\n  LayersIcon,\n  Redo2Icon,\n  SlidersHorizontalIcon,\n  SparklesIcon,\n  Undo2Icon,\n  WandSparklesIcon,\n  ZoomInIcon,\n  ZoomOutIcon,\n  type LucideIcon,\n} from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport { useImageEditor } from \"photocn/react\";\n\nexport type EditorToolbarToolId =\n  | \"adjust\"\n  | \"compose\"\n  | \"curves\"\n  | \"effects\"\n  | \"filters\"\n  | \"blender\"\n  | \"blur\"\n  | \"metadata\";\n\nexport interface EditorToolbarTool {\n  value: string;\n  label: string;\n  icon: LucideIcon;\n  disabled?: boolean;\n}\n\nexport interface EditorToolbarAction {\n  value: string;\n  label: string;\n  icon: LucideIcon;\n  onSelect: () => void;\n  disabled?: boolean;\n  variant?: ComponentProps<typeof Button>[\"variant\"];\n}\n\nexport interface EditorToolbarProps {\n  activeTool?: string;\n  tools?: readonly EditorToolbarTool[];\n  actions?: readonly EditorToolbarAction[];\n  disabled?: boolean;\n  children?: ReactNode;\n  className?: string;\n  onToolChange?: (tool: string) => void;\n}\n\nexport const editorToolbarDefaultTools = [\n  { value: \"adjust\", label: \"Adjust\", icon: SlidersHorizontalIcon },\n  { value: \"compose\", label: \"Crop\", icon: CropIcon },\n  { value: \"curves\", label: \"Curves\", icon: ActivityIcon },\n  { value: \"effects\", label: \"Effects\", icon: SparklesIcon },\n  { value: \"filters\", label: \"Filters\", icon: WandSparklesIcon },\n  { value: \"blender\", label: \"Blender\", icon: LayersIcon },\n  { value: \"blur\", label: \"Blur\", icon: DropletsIcon },\n  { value: \"metadata\", label: \"Metadata\", icon: InfoIcon },\n] as const satisfies readonly EditorToolbarTool[];\n\nexport const editorToolbarActionPresets = {\n  undo: { value: \"undo\", label: \"Undo\", icon: Undo2Icon },\n  redo: { value: \"redo\", label: \"Redo\", icon: Redo2Icon },\n  zoomOut: { value: \"zoom-out\", label: \"Zoom out\", icon: ZoomOutIcon },\n  zoomIn: { value: \"zoom-in\", label: \"Zoom in\", icon: ZoomInIcon },\n} as const;\n\nexport function EditorToolbar({\n  activeTool,\n  tools = editorToolbarDefaultTools,\n  actions = [],\n  disabled = false,\n  children,\n  className,\n  onToolChange,\n}: EditorToolbarProps) {\n  // Single selection: clicking the active tool again leaves it selected.\n  const handleToolChange = (value: string[]) => {\n    const next = value[0];\n    if (next) onToolChange?.(next);\n  };\n\n  return (\n    <TooltipProvider>\n      <div\n        aria-label=\"Editor tools\"\n        className={cn(\n          \"flex w-full min-w-0 flex-wrap items-center gap-2\",\n          className,\n        )}\n        data-slot=\"editor-toolbar\"\n        role=\"toolbar\"\n      >\n        {tools.length > 0 ? (\n          <ToggleGroup\n            aria-label=\"Editor mode\"\n            className=\"w-full min-w-0 has-[>*:nth-child(6)]:justify-between\"\n            onValueChange={handleToolChange}\n            size=\"sm\"\n            value={activeTool ? [activeTool] : []}\n            variant=\"outline\"\n          >\n            {tools.map((tool) => (\n              <ToolbarToolItem\n                disabled={disabled || tool.disabled}\n                key={tool.value}\n                tool={tool}\n              />\n            ))}\n          </ToggleGroup>\n        ) : null}\n\n        {children ? (\n          <div\n            className=\"flex min-w-0 items-center gap-2\"\n            data-slot=\"editor-toolbar-extra\"\n          >\n            {children}\n          </div>\n        ) : null}\n\n        {actions.length > 0 ? (\n          <div className=\"ml-auto flex shrink-0 items-center gap-1\">\n            {actions.map((action) => (\n              <ToolbarActionButton\n                action={{\n                  ...action,\n                  disabled: disabled || action.disabled,\n                }}\n                key={action.value}\n              />\n            ))}\n          </div>\n        ) : null}\n      </div>\n    </TooltipProvider>\n  );\n}\n\nfunction ToolbarToolItem({\n  disabled,\n  tool,\n}: {\n  disabled?: boolean;\n  tool: EditorToolbarTool;\n}) {\n  const Icon = tool.icon;\n\n  // Native `title` gives hover hints without wrapping the item in a\n  // tooltip trigger; aria-label keeps screen-reader support.\n  return (\n    <ToggleGroupItem\n      aria-label={tool.label}\n      // Strong active state — primary fill so the selected tool reads\n      // unambiguously as \"you are here\" instead of looking like a hover.\n      // Keeps hover at bg-accent for affordance.\n      className=\"min-w-0 flex-1 data-pressed:bg-primary data-pressed:text-primary-foreground data-pressed:hover:bg-primary data-pressed:hover:text-primary-foreground data-pressed:shadow-inner data-pressed:[&_svg]:scale-110\"\n      disabled={disabled}\n      title={tool.label}\n      value={tool.value}\n    >\n      <Icon aria-hidden=\"true\" />\n      <span className=\"sr-only\">{tool.label}</span>\n    </ToggleGroupItem>\n  );\n}\n\nfunction ToolbarActionButton({ action }: { action: EditorToolbarAction }) {\n  const Icon = action.icon;\n  const button = (\n    <Button\n      aria-label={action.label}\n      disabled={action.disabled}\n      onClick={action.onSelect}\n      size=\"icon-sm\"\n      type=\"button\"\n      variant={action.variant ?? \"ghost\"}\n    >\n      <Icon aria-hidden=\"true\" data-icon=\"inline-start\" />\n      <span className=\"sr-only\">{action.label}</span>\n    </Button>\n  );\n\n  return (\n    <Tooltip>\n      <TooltipTrigger render={action.disabled ? <span className=\"inline-flex\">{button}</span> : button} />\n      <TooltipContent side=\"bottom\">{action.label}</TooltipContent>\n    </Tooltip>\n  );\n}\n\nexport interface ImageEditorToolbarProps\n  extends Omit<EditorToolbarProps, \"activeTool\" | \"onToolChange\"> {}\n\n/**\n * Tool switcher (toggle group) bound to the editor's active tool. Pass\n * `tools` to show a subset or your own tools; pass `actions`/children for\n * extra buttons.\n */\nexport function ImageEditorToolbar({ disabled, ...props }: ImageEditorToolbarProps) {\n  const editor = useImageEditor();\n  return (\n    <EditorToolbar\n      activeTool={editor.tool}\n      disabled={disabled || editor.disabled || editor.isLoading}\n      onToolChange={editor.setTool}\n      {...props}\n    />\n  );\n}\n\ntype ActionButtonProps = Omit<ComponentProps<typeof Button>, \"onClick\"> & {\n  /** Show the text label next to the icon. Default `false` (icon + tooltip). */\n  showLabel?: boolean;\n};\n\nfunction EditorActionButton({\n  icon: Icon,\n  label,\n  showLabel,\n  disabled,\n  onClick,\n  size,\n  variant = \"ghost\",\n  ...props\n}: ActionButtonProps & {\n  icon: LucideIcon;\n  label: string;\n  onClick?: () => void;\n}) {\n  const button = (\n    <Button\n      aria-label={label}\n      disabled={disabled}\n      onClick={onClick}\n      size={size ?? (showLabel ? \"sm\" : \"icon-sm\")}\n      type=\"button\"\n      variant={variant}\n      {...props}\n    >\n      <Icon aria-hidden=\"true\" data-icon=\"inline-start\" />\n      {showLabel ? label : <span className=\"sr-only\">{label}</span>}\n    </Button>\n  );\n  if (showLabel) return button;\n  // Own provider so the buttons work anywhere, even in apps without one.\n  return (\n    <TooltipProvider>\n      <Tooltip>\n        <TooltipTrigger render={disabled ? <span className=\"inline-flex\">{button}</span> : button} />\n        <TooltipContent side=\"bottom\">{label}</TooltipContent>\n      </Tooltip>\n    </TooltipProvider>\n  );\n}\n\n/** Undo the last edit (Cmd/Ctrl+Z). */\nexport function ImageEditorUndoButton(props: ActionButtonProps) {\n  const editor = useImageEditor();\n  return (\n    <EditorActionButton\n      disabled={editor.disabled || !editor.history.canUndo}\n      icon={Undo2Icon}\n      label=\"Undo\"\n      onClick={editor.history.undo}\n      {...props}\n    />\n  );\n}\n\n/** Redo (Cmd/Ctrl+Shift+Z). */\nexport function ImageEditorRedoButton(props: ActionButtonProps) {\n  const editor = useImageEditor();\n  return (\n    <EditorActionButton\n      disabled={editor.disabled || !editor.history.canRedo}\n      icon={Redo2Icon}\n      label=\"Redo\"\n      onClick={editor.history.redo}\n      {...props}\n    />\n  );\n}\n\n/** Open an image from disk. */\nexport function ImageEditorOpenButton({ showLabel = true, ...props }: ActionButtonProps) {\n  const editor = useImageEditor();\n  return (\n    <EditorActionButton\n      disabled={editor.disabled || editor.isLoading}\n      icon={ImagePlusIcon}\n      label=\"Open\"\n      onClick={() => void editor.openFile()}\n      showLabel={showLabel}\n      variant=\"outline\"\n      {...props}\n    />\n  );\n}\n\n/** Discard every edit. */\nexport function ImageEditorResetButton({ showLabel = true, ...props }: ActionButtonProps) {\n  const editor = useImageEditor();\n  return (\n    <EditorActionButton\n      disabled={editor.disabled || !editor.hasImage}\n      icon={RotateCcwIcon}\n      label=\"Reset\"\n      onClick={editor.resetAll}\n      showLabel={showLabel}\n      variant=\"outline\"\n      {...props}\n    />\n  );\n}\n\n/** Press and hold to see the original (geometry is kept). */\nexport function ImageEditorCompareButton({\n  showLabel = true,\n  ...props\n}: ActionButtonProps) {\n  const editor = useImageEditor();\n  const release = () => editor.compare.setActive(false);\n  return (\n    <EditorActionButton\n      aria-pressed={editor.compare.active}\n      disabled={editor.disabled || !editor.hasImage}\n      icon={EyeIcon}\n      label=\"Original\"\n      onBlur={release}\n      onKeyDown={(event) => {\n        if (event.key === \" \" || event.key === \"Enter\") editor.compare.setActive(true);\n      }}\n      onKeyUp={release}\n      onPointerCancel={release}\n      onPointerDown={() => editor.compare.setActive(true)}\n      onPointerLeave={release}\n      onPointerUp={release}\n      showLabel={showLabel}\n      variant=\"outline\"\n      {...props}\n    />\n  );\n}\n\n",
      "type": "registry:component",
      "target": "components/image-editor/toolbar.tsx"
    }
  ],
  "categories": [
    "image-editor"
  ],
  "type": "registry:component"
}