From 56bb484fd9d545996ca7350a68dbd21777bc0e80 Mon Sep 17 00:00:00 2001 From: Kagamine Rin Date: Fri, 29 May 2026 20:41:16 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20Inline=20title=20editing=20in=20DetailP?= =?UTF-8?q?anel=20=E2=80=94=20no=20more=20JS=20prompt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DetailPanel.tsx: - Replaced window.prompt with native inline editing - Click title text or 'Rename' button to enter edit mode - Input field with lyria-accent border, focus ring, and placeholder - Auto-focus + select-all on enter edit mode (useEffect) - Enter to save, Escape to cancel, blur to save - Title text has hover:bg-white/5 for visual feedback - Non-editing title is clickable (cursor-pointer, title tooltip) - Uses useState for edit mode and draft text, useRef for input focus --- frontend/src/components/DetailPanel.tsx | 78 ++++++++++++++++++++----- 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/DetailPanel.tsx b/frontend/src/components/DetailPanel.tsx index 8c32dbe..deb09c5 100644 --- a/frontend/src/components/DetailPanel.tsx +++ b/frontend/src/components/DetailPanel.tsx @@ -1,4 +1,4 @@ -import { useRef } from 'react' +import { useRef, useState, useEffect } from 'react' interface Clip { id: string @@ -32,10 +32,40 @@ const modelName = (modelId: string) => { export default function DetailPanel({ clip, onClose, onRename, onDelete, onUploadArtwork, onRemoveArtwork }: Props) { const artworkInputRef = useRef(null) - const handleRename = () => { - const newTitle = window.prompt('Rename track:', clip.title || '') - if (newTitle !== null) { - onRename(clip.id, newTitle) + const [isEditingTitle, setIsEditingTitle] = useState(false) + const [editTitle, setEditTitle] = useState(clip.title || '') + const titleInputRef = useRef(null) + + // Focus and select when entering edit mode + useEffect(() => { + if (isEditingTitle && titleInputRef.current) { + titleInputRef.current.focus() + titleInputRef.current.select() + } + }, [isEditingTitle]) + + const startEditing = () => { + setEditTitle(clip.title || '') + setIsEditingTitle(true) + } + + const saveTitle = () => { + if (editTitle.trim() !== (clip.title || '')) { + onRename(clip.id, editTitle.trim()) + } + setIsEditingTitle(false) + } + + const cancelEditing = () => { + setEditTitle(clip.title || '') + setIsEditingTitle(false) + } + + const handleTitleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + saveTitle() + } else if (e.key === 'Escape') { + cancelEditing() } } @@ -152,20 +182,38 @@ export default function DetailPanel({ clip, onClose, onRename, onDelete, onUploa )} - {/* Title */} + {/* Title — inline editable */}
- + {!isEditingTitle && ( + + )}
-

- {clip.title || Untitled} -

+ {isEditingTitle ? ( + setEditTitle(e.target.value)} + onBlur={saveTitle} + onKeyDown={handleTitleKeyDown} + className="w-full bg-white/5 border border-lyria-accent/50 rounded-md px-3 py-2 text-white text-lg font-medium outline-none focus:border-lyria-accent focus:ring-1 focus:ring-lyria-accent/30 transition-all" + placeholder="Track title..." + /> + ) : ( +

+ {clip.title || Untitled} +

+ )}
{/* Prompt */}