{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "image-editor-layout",
  "title": "Layout",
  "description": "Resizable canvas + sidebar shell with an optional top toolbar.",
  "dependencies": [],
  "registryDependencies": [
    "resizable"
  ],
  "files": [
    {
      "path": "src/registry/image-editor/layout.tsx",
      "content": "\"use client\";\n\nimport { type ReactNode, type RefObject, useEffect, useLayoutEffect, useState } from \"react\";\n\nimport {\n  ResizableHandle,\n  ResizablePanel,\n  ResizablePanelGroup,\n} from \"@/components/ui/resizable\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface ImageEditorLayoutProps {\n  children?: ReactNode;\n  sidebar?: ReactNode;\n  toolbar?: ReactNode;\n  resizableSidebar?: boolean;\n  sidebarDefaultSize?: number | string;\n  sidebarMinSize?: number | string;\n  sidebarMaxSize?: number | string;\n  canvasMinSize?: number | string;\n  className?: string;\n  contentClassName?: string;\n  canvasClassName?: string;\n  sidebarClassName?: string;\n  toolbarClassName?: string;\n}\n\nexport function ImageEditorLayout({\n  children,\n  sidebar,\n  toolbar,\n  resizableSidebar = true,\n  sidebarDefaultSize = \"22rem\",\n  sidebarMinSize = \"18rem\",\n  sidebarMaxSize = \"34rem\",\n  canvasMinSize = \"24rem\",\n  className,\n  contentClassName,\n  canvasClassName,\n  sidebarClassName,\n  toolbarClassName,\n}: ImageEditorLayoutProps) {\n  const main = (\n    <main\n      className={cn(\n        \"h-full min-h-0 overflow-hidden bg-muted/30 p-3 md:p-4\",\n        canvasClassName,\n      )}\n      data-slot=\"editor-canvas\"\n    >\n      {children}\n    </main>\n  );\n  const sidebarPanel = sidebar ? (\n    <aside\n      className={cn(\n        \"h-full min-h-0 border-t bg-background md:border-l md:border-t-0\",\n        sidebarClassName,\n      )}\n      data-slot=\"editor-sidebar\"\n    >\n      <div className=\"@container/sidebar h-full overflow-x-hidden overflow-y-auto\">\n        {sidebar}\n      </div>\n    </aside>\n  ) : null;\n\n  return (\n    <div\n      className={cn(\n        \"grid min-h-0 w-full bg-background text-foreground\",\n        toolbar ? \"grid-rows-[auto_1fr]\" : \"grid-rows-[1fr]\",\n        className,\n      )}\n      data-slot=\"image-editor-layout\"\n    >\n      {toolbar ? (\n        <div\n          className={cn(\n            \"flex min-h-12 items-center gap-2 border-b px-3\",\n            toolbarClassName,\n          )}\n          data-slot=\"editor-toolbar\"\n        >\n          {toolbar}\n        </div>\n      ) : null}\n      {sidebarPanel && resizableSidebar ? (\n        <ResizablePanelGroup\n          className={cn(\"min-h-0\", contentClassName)}\n          data-slot=\"editor-workspace\"\n          orientation=\"horizontal\"\n        >\n          <ResizablePanel\n            groupResizeBehavior=\"preserve-relative-size\"\n            minSize={canvasMinSize}\n          >\n            {main}\n          </ResizablePanel>\n          <ResizableHandle withHandle />\n          <ResizablePanel\n            defaultSize={sidebarDefaultSize}\n            groupResizeBehavior=\"preserve-pixel-size\"\n            maxSize={sidebarMaxSize}\n            minSize={sidebarMinSize}\n          >\n            {sidebarPanel}\n          </ResizablePanel>\n        </ResizablePanelGroup>\n      ) : (\n        <div\n          className={cn(\n            \"grid min-h-0 grid-cols-1 md:grid-cols-[1fr_20rem]\",\n            contentClassName,\n          )}\n          data-slot=\"editor-workspace\"\n        >\n          {main}\n          {sidebarPanel}\n        </div>\n      )}\n    </div>\n  );\n}\n\n/** Width of an element, tracked with a ResizeObserver (0 until measured). */\n/**\n * The element's width, measured before the first paint (`null` until then),\n * so a layout can be chosen without rendering the wrong one first.\n */\nexport function useElementWidth(ref: RefObject<HTMLElement | null>): number | null {\n  const [width, setWidth] = useState<number | null>(null);\n  useIsomorphicLayoutEffect(() => {\n    const element = ref.current;\n    if (!element) return;\n    setWidth(element.getBoundingClientRect().width);\n    const observer = new ResizeObserver(([entry]) => setWidth(entry.contentRect.width));\n    observer.observe(element);\n    return () => observer.disconnect();\n  }, [ref]);\n  return width;\n}\n\nconst useIsomorphicLayoutEffect = typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\n/** The element's height, kept in sync (`null` until measured). */\nexport function useElementHeight(ref: RefObject<HTMLElement | null>): number | null {\n  const [height, setHeight] = useState<number | null>(null);\n  useIsomorphicLayoutEffect(() => {\n    const element = ref.current;\n    if (!element) return;\n    setHeight(element.getBoundingClientRect().height);\n    const observer = new ResizeObserver(([entry]) => setHeight(entry.contentRect.height));\n    observer.observe(element);\n    return () => observer.disconnect();\n  }, [ref]);\n  return height;\n}\n\n/** `window.matchMedia(query).matches`, kept in sync (false during SSR). */\nexport function useMediaQuery(query: string): boolean {\n  const [matches, setMatches] = useState(false);\n  useEffect(() => {\n    const list = window.matchMedia(query);\n    setMatches(list.matches);\n    const onChange = () => setMatches(list.matches);\n    list.addEventListener(\"change\", onChange);\n    return () => list.removeEventListener(\"change\", onChange);\n  }, [query]);\n  return matches;\n}\n\n/** Phones and small tablets: panels go in a bottom drawer. */\nexport const MOBILE_QUERY = \"(max-width: 767px)\";\n/** Below this editor width the compact (mobile) layout is used. */\nexport const COMPACT_WIDTH = 640;\n\n",
      "type": "registry:component",
      "target": "components/image-editor/layout.tsx"
    }
  ],
  "categories": [
    "image-editor"
  ],
  "type": "registry:component"
}