{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "retro-window",
  "title": "RetroWindow",
  "description": "A Mac OS 9 styled window frame with title bar, close/collapse/zoom controls, active/inactive states, and content area.",
  "registryDependencies": [
    "https://nostalgia-ui.com/r/nostalgia-theme.json",
    "https://nostalgia-ui.com/r/retro-title-bar.json"
  ],
  "files": [
    {
      "path": "registry/new-york/ui/retro-window.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { RetroTitleBar } from \"@/registry/new-york/ui/retro-title-bar\"\n\n/* ------------------------------------------------------------------ */\n/*  Types                                                              */\n/* ------------------------------------------------------------------ */\n\nexport interface RetroWindowSize {\n  width: number\n  height: number\n}\n\nexport interface RetroWindowPosition {\n  x: number\n  y: number\n}\n\nexport interface RetroWindowProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  /** Window title displayed in the title bar. */\n  title: string\n  /** Whether the window is in active/focused state. Defaults to true. */\n  active?: boolean\n  /** Callback when the close box is clicked. */\n  onClose?: React.MouseEventHandler<HTMLButtonElement>\n  /**\n   * Callback when the collapse box is clicked. Call `event.preventDefault()`\n   * to suppress the built-in window-shade toggle.\n   */\n  onCollapse?: React.MouseEventHandler<HTMLButtonElement>\n  /** Callback when the zoom box is clicked. */\n  onZoom?: React.MouseEventHandler<HTMLButtonElement>\n\n  /* ---- Window shade ---- */\n  /** Controlled window-shade state (only the title bar is shown). */\n  collapsed?: boolean\n  /** Uncontrolled initial window-shade state. Defaults to false. */\n  defaultCollapsed?: boolean\n  /** Fired when the collapse box or a title-bar double-click toggles the shade. */\n  onCollapsedChange?: (collapsed: boolean) => void\n\n  /* ---- Resize ---- */\n  /** Show a size box in the bottom-right corner. Defaults to false. */\n  resizable?: boolean\n  /** Minimum size while resizing. Defaults to 160 x 80. */\n  minWidth?: number\n  minHeight?: number\n  /** Fired continuously while resizing. */\n  onResize?: (size: RetroWindowSize) => void\n\n  /* ---- Drag ---- */\n  /** Allow dragging the window by its title bar (uses a translate transform). */\n  draggable?: boolean\n  /** Initial drag offset. Defaults to { x: 0, y: 0 }. */\n  defaultPosition?: RetroWindowPosition\n  /** Fired continuously while dragging. */\n  onPositionChange?: (position: RetroWindowPosition) => void\n\n  /* ---- Slots ---- */\n  /** Bottom row, e.g. a placard or horizontal scrollbar. Sits beside the size box. */\n  footer?: React.ReactNode\n  /** Extra classes for the content area (default has 8px padding). */\n  contentClassName?: string\n}\n\n/* ------------------------------------------------------------------ */\n/*  Size box                                                           */\n/* ------------------------------------------------------------------ */\n\nfunction SizeBox({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {\n  return (\n    <div\n      aria-hidden=\"true\"\n      className={cn(\n        \"relative size-[15px] shrink-0 cursor-nwse-resize touch-none\",\n        \"border-l border-t border-os9-black bg-os9-gray-300\",\n        \"shadow-[var(--os9-shadow-raised)]\",\n        className\n      )}\n      {...props}\n    >\n      <svg\n        width=\"13\"\n        height=\"13\"\n        viewBox=\"0 0 13 13\"\n        className=\"absolute left-0 top-0\"\n        aria-hidden=\"true\"\n      >\n        {/* Two overlapping squares — the Platinum grow box */}\n        <rect x=\"2.5\" y=\"2.5\" width=\"5\" height=\"5\" fill=\"var(--os9-gray-300)\" stroke=\"var(--os9-gray-700)\" />\n        <rect x=\"4.5\" y=\"4.5\" width=\"6\" height=\"6\" fill=\"var(--os9-gray-300)\" stroke=\"var(--os9-gray-700)\" />\n        <path d=\"M5 10V5H10\" fill=\"none\" stroke=\"var(--os9-white)\" />\n      </svg>\n    </div>\n  )\n}\n\n/* ------------------------------------------------------------------ */\n/*  RetroWindow                                                        */\n/* ------------------------------------------------------------------ */\n\nconst RetroWindow = React.forwardRef<HTMLDivElement, RetroWindowProps>(\n  function RetroWindow(\n    {\n      title,\n      active = true,\n      onClose,\n      onCollapse,\n      onZoom,\n      collapsed: collapsedProp,\n      defaultCollapsed = false,\n      onCollapsedChange,\n      resizable = false,\n      minWidth = 160,\n      minHeight = 80,\n      onResize,\n      draggable = false,\n      defaultPosition,\n      onPositionChange,\n      footer,\n      contentClassName,\n      className,\n      style,\n      children,\n      ...props\n    },\n    ref\n  ) {\n    const rootRef = React.useRef<HTMLDivElement | null>(null)\n    const setRefs = React.useCallback(\n      (node: HTMLDivElement | null) => {\n        rootRef.current = node\n        if (typeof ref === \"function\") ref(node)\n        else if (ref) ref.current = node\n      },\n      [ref]\n    )\n\n    /* ---- Window shade ---- */\n    const [internalCollapsed, setInternalCollapsed] = React.useState(defaultCollapsed)\n    const collapsed = collapsedProp ?? internalCollapsed\n    const toggleCollapsed = () => {\n      const next = !collapsed\n      if (collapsedProp === undefined) setInternalCollapsed(next)\n      onCollapsedChange?.(next)\n    }\n\n    /* ---- Resize ---- */\n    const [size, setSize] = React.useState<RetroWindowSize | null>(null)\n    const resizeRef = React.useRef<{\n      x: number\n      y: number\n      width: number\n      height: number\n      pointerId: number\n    } | null>(null)\n\n    const onResizeStart = (e: React.PointerEvent<HTMLDivElement>) => {\n      const el = rootRef.current\n      if (!el || e.button !== 0) return\n      e.preventDefault()\n      e.stopPropagation()\n      e.currentTarget.setPointerCapture(e.pointerId)\n      resizeRef.current = {\n        x: e.clientX,\n        y: e.clientY,\n        width: el.offsetWidth,\n        height: el.offsetHeight,\n        pointerId: e.pointerId,\n      }\n    }\n    const onResizeMove = (e: React.PointerEvent<HTMLDivElement>) => {\n      const start = resizeRef.current\n      if (!start || start.pointerId !== e.pointerId) return\n      const next = {\n        width: Math.max(minWidth, Math.round(start.width + e.clientX - start.x)),\n        height: Math.max(minHeight, Math.round(start.height + e.clientY - start.y)),\n      }\n      setSize(next)\n      onResize?.(next)\n    }\n    const onResizeEnd = (e: React.PointerEvent<HTMLDivElement>) => {\n      if (resizeRef.current?.pointerId !== e.pointerId) return\n      resizeRef.current = null\n      if (e.currentTarget.hasPointerCapture(e.pointerId)) {\n        e.currentTarget.releasePointerCapture(e.pointerId)\n      }\n    }\n\n    /* ---- Drag ---- */\n    const [position, setPosition] = React.useState<RetroWindowPosition>(\n      defaultPosition ?? { x: 0, y: 0 }\n    )\n    const dragRef = React.useRef<{\n      x: number\n      y: number\n      origin: RetroWindowPosition\n      pointerId: number\n    } | null>(null)\n\n    const onDragStart = (e: React.PointerEvent<HTMLDivElement>) => {\n      if (!draggable || e.button !== 0) return\n      // Let the control boxes handle their own clicks.\n      if ((e.target as HTMLElement).closest(\"button\")) return\n      e.preventDefault()\n      e.currentTarget.setPointerCapture(e.pointerId)\n      dragRef.current = {\n        x: e.clientX,\n        y: e.clientY,\n        origin: position,\n        pointerId: e.pointerId,\n      }\n    }\n    const onDragMove = (e: React.PointerEvent<HTMLDivElement>) => {\n      const start = dragRef.current\n      if (!start || start.pointerId !== e.pointerId) return\n      const next = {\n        x: start.origin.x + e.clientX - start.x,\n        y: start.origin.y + e.clientY - start.y,\n      }\n      setPosition(next)\n      onPositionChange?.(next)\n    }\n    const onDragEnd = (e: React.PointerEvent<HTMLDivElement>) => {\n      if (dragRef.current?.pointerId !== e.pointerId) return\n      dragRef.current = null\n      if (e.currentTarget.hasPointerCapture(e.pointerId)) {\n        e.currentTarget.releasePointerCapture(e.pointerId)\n      }\n    }\n\n    /* ---- Style ---- */\n    const mergedStyle: React.CSSProperties = { ...style }\n    if (size) {\n      mergedStyle.width = size.width\n      mergedStyle.height = size.height\n    }\n    if (collapsed) {\n      mergedStyle.height = \"auto\"\n      mergedStyle.minHeight = 0\n    }\n    if (draggable && (position.x !== 0 || position.y !== 0)) {\n      mergedStyle.transform = `translate(${position.x}px, ${position.y}px)`\n    }\n\n    const showSizeBox = resizable && !collapsed\n\n    return (\n      <div\n        ref={setRefs}\n        data-state={collapsed ? \"collapsed\" : \"expanded\"}\n        className={cn(\n          \"relative flex flex-col border border-os9-black bg-os9-gray-200\",\n          active && \"shadow-[var(--os9-shadow-window)]\",\n          className\n        )}\n        style={mergedStyle}\n        {...props}\n      >\n        {/* Title bar */}\n        <RetroTitleBar\n          title={title}\n          active={active}\n          onClose={onClose}\n          onCollapse={(e) => {\n            onCollapse?.(e)\n            if (!e.defaultPrevented) toggleCollapsed()\n          }}\n          onZoom={onZoom}\n          onDoubleClick={(e) => {\n            if ((e.target as HTMLElement).closest(\"button\")) return\n            toggleCollapsed()\n          }}\n          onPointerDown={onDragStart}\n          onPointerMove={onDragMove}\n          onPointerUp={onDragEnd}\n          onPointerCancel={onDragEnd}\n          onLostPointerCapture={onDragEnd}\n          className={cn(\"shrink-0\", draggable && \"touch-none select-none\")}\n        />\n\n        {!collapsed && (\n          <>\n            {/* Content area */}\n            <div\n              className={cn(\n                \"min-h-0 flex-1 p-[8px] shadow-[-1px_-1px_0_rgba(38,38,38,0.4)]\",\n                contentClassName\n              )}\n            >\n              {children}\n            </div>\n\n            {/* Footer row (+ size box) */}\n            {footer ? (\n              <div className=\"flex shrink-0 items-stretch border-t border-os9-black\">\n                <div className=\"min-w-0 flex-1\">{footer}</div>\n                {showSizeBox && (\n                  <SizeBox\n                    className=\"border-t-0\"\n                    onPointerDown={onResizeStart}\n                    onPointerMove={onResizeMove}\n                    onPointerUp={onResizeEnd}\n                    onPointerCancel={onResizeEnd}\n                    onLostPointerCapture={onResizeEnd}\n                  />\n                )}\n              </div>\n            ) : (\n              showSizeBox && (\n                <div className=\"absolute bottom-0 right-0\">\n                  <SizeBox\n                    onPointerDown={onResizeStart}\n                    onPointerMove={onResizeMove}\n                    onPointerUp={onResizeEnd}\n                    onPointerCancel={onResizeEnd}\n                    onLostPointerCapture={onResizeEnd}\n                  />\n                </div>\n              )\n            )}\n          </>\n        )}\n      </div>\n    )\n  }\n)\nRetroWindow.displayName = \"RetroWindow\"\n\nexport { RetroWindow }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}