"use client"; import { ChevronRight, GripVertical, Loader2, Save } from "lucide-react"; import { AnimatePresence, motion, type Variants } from "motion/react"; import dynamic from "next/dynamic"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { useEditorContext } from "./editor-context"; import { RecoveryBanner } from "./recovery-banner"; // Lazy load EditorPane (includes Monaco) - only loaded when drawer opens const EditorPane = dynamic( () => import("./editor/editor-pane").then((mod) => mod.EditorPane), { loading: () => (
), ssr: false, // Monaco doesn't work server-side }, ); import { Button } from "@mdxui/primitives/button"; import { Kbd } from "@mdxui/primitives/kbd"; import { cn } from "@mdxui/primitives/lib/utils"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@mdxui/primitives/tooltip"; import { useIsMobile, usePrefersReducedMotion } from "../hooks/use-media-query"; interface EditorDrawerProps { isOpen: boolean; } const MIN_WIDTH = 320; const MIN_CONTENT_WIDTH = 375; // Minimum content area when editor is open const DEFAULT_WIDTH_PERCENT = 1 / 3; // One-third of screen function getDefaultWidth() { if (typeof window === "undefined") return 500; return Math.max( MIN_WIDTH, Math.floor(window.innerWidth * DEFAULT_WIDTH_PERCENT), ); } export function EditorDrawer({ isOpen }: EditorDrawerProps) { // Get shared state from context const { path, isDirty, isSaving, onSave, onClose, recovery } = useEditorContext(); const prefersReducedMotion = usePrefersReducedMotion(); const isMobile = useIsMobile() ?? false; const [drawerWidth, setDrawerWidth] = useState(getDefaultWidth); const [isResizing, setIsResizing] = useState(false); const drawerRef = useRef(null); // Handle escape key const handleKeyDown = useCallback( (e: KeyboardEvent) => { if (e.key === "Escape" && !isResizing && onClose) { onClose(); } }, [onClose, isResizing], ); useEffect(() => { if (isOpen) { document.addEventListener("keydown", handleKeyDown); } return () => document.removeEventListener("keydown", handleKeyDown); }, [isOpen, handleKeyDown]); // Resize handling const handleResizeStart = useCallback((e: React.MouseEvent) => { e.preventDefault(); setIsResizing(true); }, []); useEffect(() => { if (!isResizing) return; let rafId: number | null = null; let latestX = 0; const handleMouseMove = (e: MouseEvent) => { latestX = e.clientX; if (rafId) return; // Skip if RAF pending rafId = requestAnimationFrame(() => { const maxWidth = window.innerWidth - MIN_CONTENT_WIDTH; const newWidth = window.innerWidth - latestX; setDrawerWidth(Math.max(MIN_WIDTH, Math.min(maxWidth, newWidth))); rafId = null; }); }; const handleMouseUp = () => { setIsResizing(false); }; document.addEventListener("mousemove", handleMouseMove, { passive: true }); document.addEventListener("mouseup", handleMouseUp); // Add cursor style to body during resize document.body.style.cursor = "ew-resize"; document.body.style.userSelect = "none"; return () => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); document.body.style.cursor = ""; document.body.style.userSelect = ""; if (rafId) cancelAnimationFrame(rafId); }; }, [isResizing]); // Set CSS custom property for drawer width (used by page content to adjust) useEffect(() => { if (typeof window === "undefined") return; const root = document.documentElement; if (!isOpen || isMobile) { // Reset when drawer closes or on mobile (full-screen overlay) root.style.removeProperty("--editor-drawer-width"); return; } root.style.setProperty("--editor-drawer-width", `${drawerWidth}px`); return () => { root.style.removeProperty("--editor-drawer-width"); }; }, [isOpen, drawerWidth, isMobile]); // Memoized animation variants for slide-in from right const drawerVariants = useMemo( () => ({ hidden: { x: "100%", }, visible: { x: 0, transition: { type: "spring", damping: 30, stiffness: 300, duration: prefersReducedMotion ? 0 : undefined, }, }, exit: { x: "100%", transition: { type: "spring", damping: 30, stiffness: 300, duration: prefersReducedMotion ? 0 : undefined, }, }, }), [prefersReducedMotion], ); // SSR guard for portal if (typeof window === "undefined") return null; return createPortal( {isOpen && ( {/* Resize handle - hidden on mobile */} {!isMobile && ( // biome-ignore lint/a11y/noStaticElementInteractions:
)} {/* Header */}
{/* Close button - left side */}

Close I

{/* File path - center */} {path || "untitled.mdx"} {/* Save button - right side */} {onSave && (

Save S

)}
{/* Recovery banner - shown when autosaved content is available */} {recovery && ( )} {/* Editor content - uses EditorContext for state */}
)}
, document.body, ); }