feat: implement input handling improvements in MemoryContent and TeamMemoryManager components, including click outside to close functionality

This commit is contained in:
Anish Sarkar 2026-04-10 15:23:04 +05:30
parent 8c9440998a
commit 27cd032d28
2 changed files with 67 additions and 13 deletions

View file

@ -34,6 +34,7 @@ export function MemoryContent() {
const [editing, setEditing] = useState(false); const [editing, setEditing] = useState(false);
const [showInput, setShowInput] = useState(false); const [showInput, setShowInput] = useState(false);
const textareaRef = useRef<HTMLInputElement>(null); const textareaRef = useRef<HTMLInputElement>(null);
const inputContainerRef = useRef<HTMLDivElement>(null);
const fetchMemory = useCallback(async () => { const fetchMemory = useCallback(async () => {
try { try {
@ -51,6 +52,26 @@ export function MemoryContent() {
fetchMemory(); fetchMemory();
}, [fetchMemory]); }, [fetchMemory]);
useEffect(() => {
if (!showInput) return;
const handlePointerDownOutside = (event: MouseEvent | TouchEvent) => {
const target = event.target;
if (!(target instanceof Node)) return;
if (inputContainerRef.current?.contains(target)) return;
setShowInput(false);
};
document.addEventListener("mousedown", handlePointerDownOutside);
document.addEventListener("touchstart", handlePointerDownOutside, { passive: true });
return () => {
document.removeEventListener("mousedown", handlePointerDownOutside);
document.removeEventListener("touchstart", handlePointerDownOutside);
};
}, [showInput]);
const handleClear = async () => { const handleClear = async () => {
try { try {
setSaving(true); setSaving(true);
@ -122,9 +143,6 @@ export function MemoryContent() {
if (e.key === "Enter" && !e.shiftKey) { if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault(); e.preventDefault();
handleEdit(); handleEdit();
} else if (e.key === "Escape") {
setShowInput(false);
setEditQuery("");
} }
}; };
@ -183,7 +201,10 @@ export function MemoryContent() {
{showInput ? ( {showInput ? (
<div className="absolute bottom-3 inset-x-3 z-10"> <div className="absolute bottom-3 inset-x-3 z-10">
<div className="relative flex items-center gap-2 rounded-full border bg-muted/60 backdrop-blur-sm px-4 py-2 shadow-sm"> <div
ref={inputContainerRef}
className="relative flex h-[54px] items-center gap-2 rounded-[9999px] border bg-muted/60 backdrop-blur-sm pl-4 pr-1 shadow-sm"
>
<input <input
ref={textareaRef} ref={textareaRef}
type="text" type="text"
@ -200,9 +221,15 @@ export function MemoryContent() {
variant="ghost" variant="ghost"
onClick={handleEdit} onClick={handleEdit}
disabled={editing || !editQuery.trim()} disabled={editing || !editQuery.trim()}
className="h-9 w-9 shrink-0 rounded-full" className={`h-11 w-11 shrink-0 rounded-full ${
editing ? "" : "bg-muted-foreground/15 hover:bg-muted-foreground/20"
}`}
> >
{editing ? <Spinner size="sm" /> : <ArrowUp className="!h-5 !w-5" />} {editing ? (
<Spinner size="sm" />
) : (
<ArrowUp className="!h-5 !w-5 text-foreground" strokeWidth={2.25} />
)}
</Button> </Button>
</div> </div>
</div> </div>

View file

@ -3,7 +3,7 @@
import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { ArrowUp, ChevronDown, ClipboardCopy, Download, Info, Pen } from "lucide-react"; import { ArrowUp, ChevronDown, ClipboardCopy, Download, Info, Pen } from "lucide-react";
import { useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import { updateSearchSpaceMutationAtom } from "@/atoms/search-spaces/search-space-mutation.atoms"; import { updateSearchSpaceMutationAtom } from "@/atoms/search-spaces/search-space-mutation.atoms";
@ -48,9 +48,30 @@ export function TeamMemoryManager({ searchSpaceId }: TeamMemoryManagerProps) {
const [editing, setEditing] = useState(false); const [editing, setEditing] = useState(false);
const [showInput, setShowInput] = useState(false); const [showInput, setShowInput] = useState(false);
const textareaRef = useRef<HTMLInputElement>(null); const textareaRef = useRef<HTMLInputElement>(null);
const inputContainerRef = useRef<HTMLDivElement>(null);
const memory = searchSpace?.shared_memory_md || ""; const memory = searchSpace?.shared_memory_md || "";
useEffect(() => {
if (!showInput) return;
const handlePointerDownOutside = (event: MouseEvent | TouchEvent) => {
const target = event.target;
if (!(target instanceof Node)) return;
if (inputContainerRef.current?.contains(target)) return;
setShowInput(false);
};
document.addEventListener("mousedown", handlePointerDownOutside);
document.addEventListener("touchstart", handlePointerDownOutside, { passive: true });
return () => {
document.removeEventListener("mousedown", handlePointerDownOutside);
document.removeEventListener("touchstart", handlePointerDownOutside);
};
}, [showInput]);
const handleClear = async () => { const handleClear = async () => {
try { try {
setSaving(true); setSaving(true);
@ -126,9 +147,6 @@ export function TeamMemoryManager({ searchSpaceId }: TeamMemoryManagerProps) {
if (e.key === "Enter" && !e.shiftKey) { if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault(); e.preventDefault();
handleEdit(); handleEdit();
} else if (e.key === "Escape") {
setShowInput(false);
setEditQuery("");
} }
}; };
@ -189,7 +207,10 @@ export function TeamMemoryManager({ searchSpaceId }: TeamMemoryManagerProps) {
{showInput ? ( {showInput ? (
<div className="absolute bottom-3 inset-x-3 z-10"> <div className="absolute bottom-3 inset-x-3 z-10">
<div className="relative flex items-center gap-2 rounded-full border bg-muted/60 backdrop-blur-sm px-4 py-2 shadow-sm"> <div
ref={inputContainerRef}
className="relative flex h-[54px] items-center gap-2 rounded-[9999px] border bg-muted/60 backdrop-blur-sm pl-4 pr-1 shadow-sm"
>
<input <input
ref={textareaRef} ref={textareaRef}
type="text" type="text"
@ -206,9 +227,15 @@ export function TeamMemoryManager({ searchSpaceId }: TeamMemoryManagerProps) {
variant="ghost" variant="ghost"
onClick={handleEdit} onClick={handleEdit}
disabled={editing || !editQuery.trim()} disabled={editing || !editQuery.trim()}
className="h-9 w-9 shrink-0 rounded-full" className={`h-11 w-11 shrink-0 rounded-full ${
editing ? "" : "bg-muted-foreground/15 hover:bg-muted-foreground/20"
}`}
> >
{editing ? <Spinner size="sm" /> : <ArrowUp className="!h-5 !w-5" />} {editing ? (
<Spinner size="sm" />
) : (
<ArrowUp className="!h-5 !w-5 text-foreground" strokeWidth={2.25} />
)}
</Button> </Button>
</div> </div>
</div> </div>