"use client"; import { useAtomValue, useSetAtom } from "jotai"; import { XIcon } from "lucide-react"; import dynamic from "next/dynamic"; import { useCallback, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; import { Drawer, DrawerContent, DrawerHandle, DrawerTitle } from "@/components/ui/drawer"; import { Skeleton } from "@/components/ui/skeleton"; import { useMediaQuery } from "@/hooks/use-media-query"; import { closeHitlEditPanelAtom, type ExtraField, hitlEditPanelAtom } from "./edit-panel.atom"; import { ExtraFieldsSection } from "./fields"; const PlateEditor = dynamic( () => import("@/components/editor/plate-editor").then((m) => ({ default: m.PlateEditor })), { ssr: false, loading: () => } ); /** * The actual editable form. Controlled by atom data via the * Desktop/Mobile shells below; isolated from layout so the same form * renders identically in either container. */ export function HitlEditPanelContent({ title: initialTitle, content: initialContent, contentFormat, extraFields, onSave, onClose, showCloseButton = true, }: { title: string; content: string; toolName: string; contentFormat?: "markdown" | "html"; extraFields?: ExtraField[]; onSave: (title: string, content: string, extraFieldValues?: Record) => void; onClose?: () => void; showCloseButton?: boolean; }) { const [editedTitle, setEditedTitle] = useState(initialTitle); const contentRef = useRef(initialContent); const [isSaving, setIsSaving] = useState(false); const [extraFieldValues, setExtraFieldValues] = useState>(() => { if (!extraFields) return {}; const initial: Record = {}; for (const field of extraFields) { initial[field.key] = field.value; } return initial; }); const handleContentChange = useCallback((content: string) => { contentRef.current = content; }, []); const handleExtraFieldChange = useCallback((key: string, value: string) => { setExtraFieldValues((prev) => ({ ...prev, [key]: value })); }, []); const handleSave = useCallback(() => { if (!editedTitle.trim()) return; setIsSaving(true); const extras = extraFields && extraFields.length > 0 ? extraFieldValues : undefined; onSave(editedTitle, contentRef.current, extras); onClose?.(); }, [editedTitle, onSave, onClose, extraFields, extraFieldValues]); return ( <>
setEditedTitle(e.target.value)} placeholder="Untitled" className="flex-1 min-w-0 bg-transparent text-sm font-semibold text-foreground outline-none placeholder:text-muted-foreground" aria-label="Page title" /> {onClose && showCloseButton && ( )}
{extraFields && extraFields.length > 0 && ( )}
); } function DesktopHitlEditPanel() { const panelState = useAtomValue(hitlEditPanelAtom); const closePanel = useSetAtom(closeHitlEditPanelAtom); if (!panelState.isOpen || !panelState.onSave) return null; return (
); } function MobileHitlEditDrawer() { const panelState = useAtomValue(hitlEditPanelAtom); const closePanel = useSetAtom(closeHitlEditPanelAtom); if (!panelState.onSave) return null; return ( { if (!open) closePanel(); }} shouldScaleBackground={false} > Edit {panelState.toolName}
); } /** * Entry point mounted by the right-panel layout. Renders the desktop * panel on lg+ and the mobile drawer below; both share state via the * ``hitlEditPanelAtom``. */ export function HitlEditPanel() { const panelState = useAtomValue(hitlEditPanelAtom); const isDesktop = useMediaQuery("(min-width: 1024px)"); if (!panelState.isOpen) return null; if (isDesktop) { return ; } return ; } /** * Entry point mounted by chat pages so the mobile drawer can render * outside the desktop right-panel container. */ export function MobileHitlEditPanel() { const panelState = useAtomValue(hitlEditPanelAtom); const isDesktop = useMediaQuery("(min-width: 1024px)"); if (isDesktop || !panelState.isOpen) return null; return ; }