feat: Inline title editing in DetailPanel — no more JS prompt

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
This commit is contained in:
rin.kagamine committed 2026-05-29 20:41:16 -04:00
1 parent 16f2dd6788
commit 56bb484fd9
1 file changed
+63 -15
+63 -15
View File
@@ -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<HTMLInputElement>(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<HTMLInputElement>(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
</div>
)}
{/* Title */}
{/* Title — inline editable */}
<div>
<div className="flex items-center justify-between mb-1">
<label className="text-xs text-lyria-muted uppercase tracking-wider">Title</label>
<button
onClick={handleRename}
className="text-xs text-lyria-accent hover:text-lyria-accent/80 transition-colors"
>
Rename
</button>
{!isEditingTitle && (
<button
onClick={startEditing}
className="text-xs text-lyria-accent hover:text-lyria-accent/80 transition-colors"
>
Rename
</button>
)}
</div>
<p className="text-white text-lg font-medium">
{clip.title || <span className="text-lyria-muted italic">Untitled</span>}
</p>
{isEditingTitle ? (
<input
ref={titleInputRef}
type="text"
value={editTitle}
onChange={(e) => 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..."
/>
) : (
<p className="text-white text-lg font-medium px-3 py-2 -mx-3 rounded-md hover:bg-white/5 transition-colors cursor-pointer"
onClick={startEditing}
title="Click to rename"
>
{clip.title || <span className="text-lyria-muted italic">Untitled</span>}
</p>
)}
</div>
{/* Prompt */}