({ page, onGenerate }: HeaderProps)
| 12 | } |
| 13 | |
| 14 | export function Header({ page, onGenerate }: HeaderProps): React.JSX.Element { |
| 15 | const [isEditing, setIsEditing] = useState(false) |
| 16 | const [editValue, setEditValue] = useState(page.name) |
| 17 | const [popoverOpen, setPopoverOpen] = useState(false) |
| 18 | const inputRef = useRef<InputRef>(null) |
| 19 | |
| 20 | // 同步外部 name 变化 |
| 21 | useEffect(() => { |
| 22 | if (!isEditing) { |
| 23 | setEditValue(page.name) |
| 24 | } |
| 25 | }, [page.name, isEditing]) |
| 26 | |
| 27 | // 进入编辑模式时聚焦输入框 |
| 28 | useEffect(() => { |
| 29 | if (isEditing) { |
| 30 | inputRef.current?.focus() |
| 31 | inputRef.current?.select() |
| 32 | } |
| 33 | }, [isEditing]) |
| 34 | |
| 35 | const handleEdit = useCallback(() => { |
| 36 | setEditValue(page.name) |
| 37 | setIsEditing(true) |
| 38 | }, [page.name]) |
| 39 | |
| 40 | const handleSave = useCallback(async () => { |
| 41 | const trimmed = editValue.trim() |
| 42 | if (trimmed && trimmed !== page.name) { |
| 43 | await updatePage(page.id, { name: trimmed }) |
| 44 | } |
| 45 | setIsEditing(false) |
| 46 | }, [editValue, page.id, page.name]) |
| 47 | |
| 48 | const handleCancel = useCallback(() => { |
| 49 | setEditValue(page.name) |
| 50 | setIsEditing(false) |
| 51 | }, [page.name]) |
| 52 | |
| 53 | const handleKeyDown = useCallback( |
| 54 | (e: React.KeyboardEvent) => { |
| 55 | if (e.key === 'Enter') { |
| 56 | handleSave() |
| 57 | } else if (e.key === 'Escape') { |
| 58 | handleCancel() |
| 59 | } |
| 60 | }, |
| 61 | [handleSave, handleCancel] |
| 62 | ) |
| 63 | |
| 64 | const handleAIGenerate = useCallback( |
| 65 | async (options: GenerateOptions) => { |
| 66 | if (onGenerate) { |
| 67 | await onGenerate(options) |
| 68 | } |
| 69 | setIsEditing(false) |
| 70 | }, |
| 71 | [onGenerate] |
nothing calls this directly
no test coverage detected