"use client"; import { useEffect, useRef, useState, type RefObject } from "react"; import { createPortal } from "react-dom"; import { Settings2 } from "lucide-react"; import { Button } from "antd"; import { ImageSettingsPanel, imageQualityLabel, imageSizeLabel } from "@/components/image-settings-panel"; import { canvasThemes } from "@/lib/canvas-theme"; import { useThemeStore } from "@/stores/use-theme-store"; import type { AiConfig } from "@/stores/use-config-store"; type CanvasImageSettingsPopoverProps = { config: AiConfig; onConfigChange: (key: keyof AiConfig, value: string) => void; onMissingConfig?: () => void; onOpenChange?: (open: boolean) => void; buttonClassName?: string; getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement; placement?: "topLeft" | "top" | "topRight" | "bottomLeft" | "bottom" | "bottomRight"; autoAdjustOverflow?: boolean; }; export function CanvasImageSettingsPopover({ config, onConfigChange, onOpenChange, buttonClassName, placement = "topLeft" }: CanvasImageSettingsPopoverProps) { const theme = canvasThemes[useThemeStore((state) => state.theme)]; const buttonRef = useRef(null); const panelRef = useRef(null); const [open, setOpen] = useState(false); const [buttonRect, setButtonRect] = useState(null); const quality = config.quality || "auto"; const count = Math.max(1, Math.min(15, Math.floor(Math.abs(Number(config.count)) || 1))); const activeSize = config.size || "auto"; const updateOpen = (nextOpen: boolean) => { setOpen(nextOpen); onOpenChange?.(nextOpen); }; useEffect(() => { if (!open) return; const syncPosition = () => setButtonRect(buttonRef.current?.getBoundingClientRect() || null); const closeOnOutsidePointer = (event: PointerEvent) => { const target = event.target; if (!(target instanceof Node)) return; if (buttonRef.current?.contains(target) || panelRef.current?.contains(target)) return; setOpen(false); onOpenChange?.(false); }; syncPosition(); window.addEventListener("resize", syncPosition); window.addEventListener("scroll", syncPosition, true); window.addEventListener("pointerdown", closeOnOutsidePointer, true); return () => { window.removeEventListener("resize", syncPosition); window.removeEventListener("scroll", syncPosition, true); window.removeEventListener("pointerdown", closeOnOutsidePointer, true); }; }, [onOpenChange, open]); const panel = open && buttonRect ? : null; return ( <> {panel} ); } function ImageSettingsPortal({ buttonRect, panelRef, placement, theme, config, onConfigChange, }: { buttonRect: DOMRect; panelRef: RefObject; placement: CanvasImageSettingsPopoverProps["placement"]; theme: (typeof canvasThemes)[keyof typeof canvasThemes]; config: AiConfig; onConfigChange: (key: keyof AiConfig, value: string) => void; }) { const width = 356; const gap = 8; const margin = 12; const alignRight = placement?.endsWith("Right"); const alignCenter = placement === "top" || placement === "bottom"; const left = alignCenter ? buttonRect.left + buttonRect.width / 2 - width / 2 : alignRight ? buttonRect.right - width : buttonRect.left; const topPlacement = placement?.startsWith("top"); const style = { position: "fixed", zIndex: 1200, width, left: Math.max(margin, Math.min(window.innerWidth - width - margin, left)), ...(topPlacement ? { bottom: window.innerHeight - buttonRect.top + gap, maxHeight: Math.max(260, buttonRect.top - margin * 2) } : { top: buttonRect.bottom + gap, maxHeight: Math.max(260, window.innerHeight - buttonRect.bottom - margin * 2) }), background: theme.toolbar.panel, borderRadius: 18, boxShadow: "0 18px 54px rgba(28, 25, 23, 0.16)", padding: 18, overflowY: "auto", color: theme.node.text, } as const; return createPortal(
event.stopPropagation()} onMouseDown={(event) => event.stopPropagation()} onClick={(event) => event.stopPropagation()} > onConfigChange(key, value)} theme={theme} className="space-y-4" />
, document.body, ); }