import { useEffect, useRef, useState } from "react"; import type { IssueWorkMode } from "@paperclipai/shared"; import { Check, ClipboardList, MessageCircleQuestion, Lock, Paperclip, Plus, Target, X, type LucideIcon } from "lucide-react"; import { cn } from "@/lib/utils"; import { useMobileEntityPickerViewportStyle } from "@/hooks/useMobileEntityPickerViewportStyle"; import { workModeMetaFor } from "@/lib/work-mode-meta"; import { Dialog, DialogClose, DialogContent, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import "./composer-run-settings.css"; const MOBILE_SHELL_QUERY = "(max-width: 767px)"; interface ComposerAddMenuProps { mode: IssueWorkMode; onModeChange?: (mode: IssueWorkMode) => void; onAttachFile?: () => void; attachDisabled?: boolean; onGoal?: () => void; privacy?: { private: boolean; inherited?: string; onChange: (value: boolean) => void }; disabled?: boolean; mobile?: boolean; triggerTestId?: string; menuTestId?: string; } export function ComposerAddMenu({ mode, onModeChange, onAttachFile, attachDisabled, onGoal, privacy, disabled, mobile: mobileProp, triggerTestId, menuTestId, }: ComposerAddMenuProps) { const [open, setOpen] = useState(false); const mobileViewportStyle = useMobileEntityPickerViewportStyle(); const goalFocusRef = useRef(false); const [narrow, setNarrow] = useState(() => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(MOBILE_SHELL_QUERY).matches); useEffect(() => { if (typeof window.matchMedia !== "function") return; const query = window.matchMedia(MOBILE_SHELL_QUERY); const update = () => setNarrow(query.matches); query.addEventListener("change", update); return () => query.removeEventListener("change", update); }, []); const mobile = mobileProp ?? narrow; if (!onModeChange && !onAttachFile && !onGoal && !privacy) return null; const actions: Array<{ id: string; label: string; detail?: string; Icon: LucideIcon; select: () => void; disabled?: boolean; selected?: boolean }> = [ ...(onAttachFile ? [{ id: "composer-add-file", label: "Files and images", Icon: Paperclip, select: onAttachFile, disabled: attachDisabled }] : []), ...(privacy ? [{ id: "composer-add-private", label: "Private task", detail: privacy.inherited, Icon: Lock, select: () => privacy.onChange(!privacy.private), selected: privacy.private, disabled: Boolean(privacy.inherited) }] : []), ...(onGoal ? [{ id: "composer-add-goal", label: "Goal", detail: "Keep pursuing", Icon: Target, select: onGoal }] : []), ...(onModeChange ? [ { id: "composer-add-plan", label: "Plan mode", detail: "Plan before acting", Icon: ClipboardList, select: () => onModeChange(mode === "planning" ? "standard" : "planning"), selected: mode === "planning" }, { id: "composer-add-ask", label: "Ask mode", detail: "Answer without changes", Icon: MessageCircleQuestion, select: () => onModeChange(mode === "ask" ? "standard" : "ask"), selected: mode === "ask" }, ] : []), ]; const trigger = ; const content = (action: typeof actions[number]) => <> {action.label}{action.detail ? {action.detail} : null} {action.selected ? : null} >; if (mobile) return {trigger} { if (goalFocusRef.current) { event.preventDefault(); goalFocusRef.current = false; } }} className="composer-mobile-dialog flex flex-col translate-y-0 gap-0 overflow-hidden p-0 md:translate-y-0"> Add {actions.map((action) => { goalFocusRef.current = action.id === "composer-add-goal"; action.select(); setOpen(false); }} className="flex min-h-11 w-full items-center gap-3 rounded-md px-2 text-left text-sm hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50"> {content(action)} )} ; return {trigger} Add {actions.map((action) => {content(action)} )} ; } interface ComposerModeChipProps { mode: IssueWorkMode; onRemove?: () => void; disabled?: boolean; testId?: string; mobile?: boolean; } export function ComposerModeChip({ mode, onRemove, disabled, testId, mobile = false }: ComposerModeChipProps) { if (mode === "standard") return null; const meta = workModeMetaFor(mode); const Icon = meta.icon; return {meta.label} ; } export function ComposerPrivacyChip({ inherited, onRemove, disabled }: { inherited?: string; onRemove: () => void; disabled?: boolean }) { const [hintOpen, setHintOpen] = useState(false); const explanation = inherited ?? "Only you and people you share with can read this task"; return { event.preventDefault(); setHintOpen(true); } : undefined} onKeyDown={inherited ? (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); setHintOpen(true); } } : undefined}> Private {!inherited ? : null} {explanation} ; }