{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "window",
  "type": "registry:component",
  "title": "Window",
  "description": "A draggable, resizable window inside a container.",
  "dependencies": [],
  "files": [
    {
      "path": "registry/new-york/blocks/window/window.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ntype Bounds = { width: number; height: number } | null;\n\nconst WindowContainerContext = React.createContext<{\n  getRect: () => DOMRect | null;\n  bumpZ: () => number;\n  topZ: number;\n} | null>(null);\n\nexport interface WindowContainerProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nexport function WindowContainer({ className, children, ...props }: WindowContainerProps) {\n  const containerRef = React.useRef<HTMLDivElement | null>(null);\n  const zCounterRef = React.useRef<number>(10);\n  const [topZ, setTopZ] = React.useState<number>(zCounterRef.current);\n\n  const getRect = React.useCallback((): DOMRect | null => {\n    const el = containerRef.current;\n    if (!el) return null;\n    return el.getBoundingClientRect();\n  }, []);\n\n  const bumpZ = React.useCallback((): number => {\n    zCounterRef.current = zCounterRef.current + 1;\n    setTopZ(zCounterRef.current);\n    return zCounterRef.current;\n  }, []);\n\n  return (\n    <WindowContainerContext.Provider value={{ getRect, bumpZ, topZ }}>\n      <div\n        ref={containerRef}\n        data-window-container\n        className={cn(\"relative w-full h-full\", className)}\n        {...props}\n      >\n        {children}\n      </div>\n    </WindowContainerContext.Provider>\n  );\n}\n\nfunction useContainerBounds() {\n  const ctx = React.useContext(WindowContainerContext);\n  return ctx?.getRect ?? (() => null);\n}\n\nfunction useZOrder() {\n  const ctx = React.useContext(WindowContainerContext);\n  return { bumpZ: ctx?.bumpZ ?? (() => 1), topZ: ctx?.topZ ?? 1 };\n}\n\nexport interface WindowProps extends React.HTMLAttributes<HTMLDivElement> {\n  title?: string;\n  initialX?: number;\n  initialY?: number;\n  initialWidth?: number;\n  initialHeight?: number;\n  minWidth?: number;\n  minHeight?: number;\n  onClose?: () => void;\n}\n\nexport function Window({\n  className,\n  children,\n  title = \"Window\",\n  initialX = 24,\n  initialY = 24,\n  initialWidth = 360,\n  initialHeight = 220,\n  minWidth = 240,\n  minHeight = 120,\n  ...props\n}: WindowProps) {\n  const getRect = useContainerBounds();\n  const { bumpZ, topZ } = useZOrder();\n\n  const [position, setPosition] = React.useState({ x: initialX, y: initialY });\n  const [size, setSize] = React.useState({ width: initialWidth, height: initialHeight });\n  const [isDragging, setIsDragging] = React.useState(false);\n  const [isResizing, setIsResizing] = React.useState<\n    | null\n    | \"right\"\n    | \"left\"\n    | \"top\"\n    | \"bottom\"\n    | \"top-right\"\n    | \"top-left\"\n    | \"bottom-right\"\n    | \"bottom-left\"\n  >(null);\n  const dragOffsetRef = React.useRef({ dx: 0, dy: 0 });\n  const HEADER_HEIGHT_PX = 36; // 2.25rem at 16px base\n\n  const [hidden, setHidden] = React.useState(false);\n  const [isMinimized, setIsMinimized] = React.useState(false);\n  const [isMaximized, setIsMaximized] = React.useState(false);\n  const prevForMaximizeRef = React.useRef<{\n    x: number;\n    y: number;\n    width: number;\n    height: number;\n  } | null>(null);\n  const prevHeightForMinimizeRef = React.useRef<number | null>(null);\n  const [animateNext, setAnimateNext] = React.useState(false);\n  const isInteracting = isDragging || Boolean(isResizing);\n  const [zIndex, setZIndex] = React.useState<number>(() => bumpZ());\n  const isFocused = zIndex === topZ;\n\n  React.useEffect(() => {\n    if (!animateNext) return;\n    const id = window.setTimeout(() => setAnimateNext(false), 320);\n    return () => window.clearTimeout(id);\n  }, [animateNext]);\n\n  const clampToBounds = React.useCallback(\n    (x: number, y: number, width: number, height: number) => {\n      const rect = getRect();\n      if (!rect) return { x, y };\n      const maxX = Math.max(0, rect.width - width);\n      const maxY = Math.max(0, rect.height - height);\n      return {\n        x: Math.min(Math.max(0, x), maxX),\n        y: Math.min(Math.max(0, y), maxY),\n      };\n    },\n    [getRect]\n  );\n\n  const onHeaderPointerDown = (e: React.PointerEvent) => {\n    (e.target as Element).setPointerCapture?.(e.pointerId);\n    // Avoid starting a drag if this pointerdown is part of a double-click (handled separately)\n    if ((e as any).detail >= 2) return;\n    setIsDragging(true);\n    setZIndex(bumpZ());\n    const rect = getRect();\n    const localX = rect ? e.clientX - rect.left : e.clientX;\n    const localY = rect ? e.clientY - rect.top : e.clientY;\n    dragOffsetRef.current = {\n      dx: localX - position.x,\n      dy: localY - position.y,\n    };\n  };\n\n  const onResizePointerDown = (\n    e: React.PointerEvent,\n    direction:\n      | \"right\"\n      | \"left\"\n      | \"top\"\n      | \"bottom\"\n      | \"top-right\"\n      | \"top-left\"\n      | \"bottom-right\"\n      | \"bottom-left\"\n  ) => {\n    (e.target as Element).setPointerCapture?.(e.pointerId);\n    setAnimateNext(false);\n    setIsResizing(direction);\n    setZIndex(bumpZ());\n  };\n\n  React.useEffect(() => {\n    const handleMove = (e: PointerEvent) => {\n      const rect = getRect();\n      const localX = rect ? e.clientX - rect.left : e.clientX;\n      const localY = rect ? e.clientY - rect.top : e.clientY;\n      if (isDragging) {\n        const nextX = localX - dragOffsetRef.current.dx;\n        const nextY = localY - dragOffsetRef.current.dy;\n        const clamped = clampToBounds(nextX, nextY, size.width, size.height);\n        setPosition(clamped);\n      } else if (isResizing) {\n        if (!rect) return;\n        if (isResizing === \"right\" || isResizing === \"bottom-right\" || isResizing === \"top-right\") {\n          const maxWidth = Math.max(minWidth, rect.width - position.x);\n          const desired = localX - position.x; // width based on pointer\n          const newWidth = Math.max(minWidth, Math.min(maxWidth, desired));\n          setSize((s) => ({ ...s, width: newWidth }));\n        }\n        if (isResizing === \"left\" || isResizing === \"bottom-left\" || isResizing === \"top-left\") {\n          const minX = 0;\n          const maxX = position.x + size.width - minWidth;\n          const newX = Math.max(minX, Math.min(maxX, localX));\n          const newWidth = Math.max(minWidth, size.width + (position.x - newX));\n          setPosition((p) => ({ ...p, x: newX }));\n          setSize((s) => ({ ...s, width: newWidth }));\n        }\n        if (\n          isResizing === \"bottom\" ||\n          isResizing === \"bottom-right\" ||\n          isResizing === \"bottom-left\"\n        ) {\n          const maxHeight = Math.max(minHeight, rect.height - position.y);\n          const desired = localY - position.y; // height based on pointer\n          const newHeight = Math.max(minHeight, Math.min(maxHeight, desired));\n          setSize((s) => ({ ...s, height: newHeight }));\n        }\n        if (isResizing === \"top\" || isResizing === \"top-right\" || isResizing === \"top-left\") {\n          const minY = 0;\n          const maxY = position.y + size.height - minHeight;\n          const newY = Math.max(minY, Math.min(maxY, localY));\n          const newHeight = Math.max(minHeight, size.height + (position.y - newY));\n          setPosition((p) => ({ ...p, y: newY }));\n          setSize((s) => ({ ...s, height: newHeight }));\n        }\n      }\n    };\n\n    const handleUp = () => {\n      setIsDragging(false);\n      setIsResizing(null);\n      // Ensure position remains within bounds after interactions\n      setPosition((p) => clampToBounds(p.x, p.y, size.width, size.height));\n    };\n\n    window.addEventListener(\"pointermove\", handleMove);\n    window.addEventListener(\"pointerup\", handleUp);\n    return () => {\n      window.removeEventListener(\"pointermove\", handleMove);\n      window.removeEventListener(\"pointerup\", handleUp);\n    };\n  }, [\n    isDragging,\n    isResizing,\n    clampToBounds,\n    getRect,\n    minHeight,\n    minWidth,\n    position.x,\n    position.y,\n    size.width,\n    size.height,\n  ]);\n\n  // Keep inside bounds if container size changes\n  React.useEffect(() => {\n    const ro = new ResizeObserver(() => {\n      setPosition((p) => clampToBounds(p.x, p.y, size.width, size.height));\n    });\n    // Try to observe the nearest container element in DOM tree\n    const containerEl = document?.querySelector?.(\"[data-window-container]\") as HTMLElement | null;\n    if (containerEl) ro.observe(containerEl);\n    return () => ro.disconnect();\n  }, [clampToBounds, size.width, size.height]);\n\n  return hidden ? null : (\n    <div\n      className={cn(\n        \"absolute select-none rounded-xl border border-border bg-card shadow-lg shadow-shade overflow-hidden\",\n        animateNext && !isInteracting && \"transition-all duration-300 ease-out\",\n        isDragging && \"cursor-grabbing\",\n        className\n      )}\n      style={{ left: position.x, top: position.y, width: size.width, height: size.height, zIndex }}\n      {...props}\n      onPointerDown={() => setZIndex(bumpZ())}\n    >\n      <div\n        className={cn(\n          \"flex items-center justify-between px-3 py-2 bg-muted text-muted-foreground cursor-grab active:cursor-grabbing\"\n        )}\n        onPointerDown={onHeaderPointerDown}\n        onDoubleClick={(e) => {\n          e.stopPropagation();\n          const rect = getRect();\n          if (!rect) return;\n          setAnimateNext(true);\n          if (!isMaximized) {\n            prevForMaximizeRef.current = {\n              x: position.x,\n              y: position.y,\n              width: size.width,\n              height: size.height,\n            };\n            setPosition({ x: 0, y: 0 });\n            setSize({ width: rect.width, height: rect.height });\n            setIsMaximized(true);\n            setIsMinimized(false);\n          } else {\n            const prev = prevForMaximizeRef.current;\n            if (prev) {\n              setPosition({ x: prev.x, y: prev.y });\n              setSize({ width: prev.width, height: prev.height });\n            }\n            setIsMaximized(false);\n          }\n        }}\n        aria-label=\"Drag window\"\n      >\n        <div\n          className=\"flex items-center gap-1.5 cursor-default\"\n          onPointerDown={(e) => e.stopPropagation()}\n        >\n          <button\n            type=\"button\"\n            onClick={() => {\n              if (typeof props.onClose === \"function\") props.onClose();\n              else setHidden(true);\n            }}\n            title=\"Close\"\n            className=\"size-3 rounded-full bg-red-500 hover:brightness-90 active:brightness-95 transition-all\"\n            aria-label=\"Close window\"\n          />\n          <button\n            type=\"button\"\n            onClick={() => {\n              setAnimateNext(true);\n              if (!isMinimized) {\n                prevHeightForMinimizeRef.current = size.height;\n                setSize((s) => ({ ...s, height: HEADER_HEIGHT_PX }));\n                setIsMinimized(true);\n              } else {\n                const prev = prevHeightForMinimizeRef.current ?? initialHeight;\n                setSize((s) => ({ ...s, height: prev }));\n                setIsMinimized(false);\n              }\n            }}\n            title=\"Minimize\"\n            className=\"size-3 rounded-full bg-yellow-400 hover:brightness-90 active:brightness-95 transition-all\"\n            aria-label=\"Minimize window\"\n          />\n          <button\n            type=\"button\"\n            onClick={() => {\n              setAnimateNext(true);\n              const rect = getRect();\n              if (!rect) return;\n              if (!isMaximized) {\n                prevForMaximizeRef.current = {\n                  x: position.x,\n                  y: position.y,\n                  width: size.width,\n                  height: size.height,\n                };\n                setPosition({ x: 0, y: 0 });\n                setSize({ width: rect.width, height: rect.height });\n                setIsMaximized(true);\n                setIsMinimized(false);\n              } else {\n                const prev = prevForMaximizeRef.current;\n                if (prev) {\n                  setPosition({ x: prev.x, y: prev.y });\n                  setSize({ width: prev.width, height: prev.height });\n                }\n                setIsMaximized(false);\n              }\n            }}\n            title=\"Maximize\"\n            className=\"size-3 rounded-full bg-green-500 hover:brightness-90 active:brightness-95 transition-all\"\n            aria-label=\"Maximize window\"\n          />\n        </div>\n        <span\n          className={cn(\n            \"text-xs truncate tracking-wide px-2 flex-1 min-w-0 text-center mr-12\",\n            isFocused ? \"font-bold\" : \"font-medium\"\n          )}\n        >\n          {title}\n        </span>\n      </div>\n      <div className=\"w-full h-[calc(100%-2.25rem)] p-3 overflow-auto\">{children}</div>\n      {/* Resize handles */}\n      <div\n        className=\"absolute right-0 top-0 h-full w-1 cursor-ew-resize\"\n        onPointerDown={(e) => onResizePointerDown(e, \"right\")}\n        aria-label=\"Resize right\"\n      />\n      <div\n        className=\"absolute left-0 top-0 h-full w-1 cursor-ew-resize\"\n        onPointerDown={(e) => onResizePointerDown(e, \"left\")}\n        aria-label=\"Resize left\"\n      />\n      <div\n        className=\"absolute left-0 bottom-0 w-full h-1 cursor-ns-resize\"\n        onPointerDown={(e) => onResizePointerDown(e, \"bottom\")}\n        aria-label=\"Resize bottom\"\n      />\n      <div\n        className=\"absolute left-0 top-0 w-full h-1 cursor-ns-resize\"\n        onPointerDown={(e) => onResizePointerDown(e, \"top\")}\n        aria-label=\"Resize top\"\n      />\n      <div\n        className=\"absolute right-0 bottom-0 size-3 cursor-nwse-resize\"\n        onPointerDown={(e) => onResizePointerDown(e, \"bottom-right\")}\n        aria-label=\"Resize corner\"\n      />\n      <div\n        className=\"absolute left-0 bottom-0 size-3 cursor-nesw-resize\"\n        onPointerDown={(e) => onResizePointerDown(e, \"bottom-left\")}\n        aria-label=\"Resize corner\"\n      />\n      <div\n        className=\"absolute right-0 top-0 size-3 cursor-nesw-resize\"\n        onPointerDown={(e) => onResizePointerDown(e, \"top-right\")}\n        aria-label=\"Resize corner\"\n      />\n      <div\n        className=\"absolute left-0 top-0 size-3 cursor-nwse-resize\"\n        onPointerDown={(e) => onResizePointerDown(e, \"top-left\")}\n        aria-label=\"Resize corner\"\n      />\n    </div>\n  );\n}\n\nexport default Window;\n",
      "type": "registry:component"
    }
  ]
}