diff options
Diffstat (limited to 'renderer/src/components/Header.tsx')
| -rw-r--r-- | renderer/src/components/Header.tsx | 81 |
1 files changed, 66 insertions, 15 deletions
diff --git a/renderer/src/components/Header.tsx b/renderer/src/components/Header.tsx index b4faa6e..a435519 100644 --- a/renderer/src/components/Header.tsx +++ b/renderer/src/components/Header.tsx | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | import React from "react"; | 1 | import React, { useState } from "react"; |
| 2 | import type { Project, Session, Phase } from "../types"; | 2 | import type { Project, Session, Phase } from "../types"; |
| 3 | 3 | ||
| 4 | type Theme = "dark" | "light"; | 4 | type Theme = "dark" | "light"; |
| @@ -14,6 +14,7 @@ interface HeaderProps { | |||
| 14 | onCreateSession: () => void; | 14 | onCreateSession: () => void; |
| 15 | onDeleteProject?: (id: string) => void; | 15 | onDeleteProject?: (id: string) => void; |
| 16 | onDeleteSession?: (id: string) => void; | 16 | onDeleteSession?: (id: string) => void; |
| 17 | onRenameSession?: (id: string, name: string) => void; | ||
| 17 | theme: Theme; | 18 | theme: Theme; |
| 18 | onToggleTheme: () => void; | 19 | onToggleTheme: () => void; |
| 19 | } | 20 | } |
| @@ -37,6 +38,7 @@ export function Header({ | |||
| 37 | onCreateSession, | 38 | onCreateSession, |
| 38 | onDeleteProject, | 39 | onDeleteProject, |
| 39 | onDeleteSession, | 40 | onDeleteSession, |
| 41 | onRenameSession, | ||
| 40 | theme, | 42 | theme, |
| 41 | onToggleTheme, | 43 | onToggleTheme, |
| 42 | }: HeaderProps) { | 44 | }: HeaderProps) { |
| @@ -54,6 +56,32 @@ export function Header({ | |||
| 54 | } | 56 | } |
| 55 | }; | 57 | }; |
| 56 | 58 | ||
| 59 | const [isRenamingSession, setIsRenamingSession] = useState(false); | ||
| 60 | const [renameValue, setRenameValue] = useState(""); | ||
| 61 | // Guard against double-commit (onKeyDown Enter → unmount → onBlur) | ||
| 62 | const renameCommitted = React.useRef(false); | ||
| 63 | |||
| 64 | const startRename = () => { | ||
| 65 | if (!selectedSession) return; | ||
| 66 | renameCommitted.current = false; | ||
| 67 | setRenameValue(selectedSession.name); | ||
| 68 | setIsRenamingSession(true); | ||
| 69 | }; | ||
| 70 | |||
| 71 | const commitRename = () => { | ||
| 72 | if (renameCommitted.current) return; | ||
| 73 | renameCommitted.current = true; | ||
| 74 | if (selectedSession && onRenameSession && renameValue.trim()) { | ||
| 75 | onRenameSession(selectedSession.id, renameValue.trim()); | ||
| 76 | } | ||
| 77 | setIsRenamingSession(false); | ||
| 78 | }; | ||
| 79 | |||
| 80 | const cancelRename = () => { | ||
| 81 | renameCommitted.current = true; // prevent blur from committing after cancel | ||
| 82 | setIsRenamingSession(false); | ||
| 83 | }; | ||
| 84 | |||
| 57 | return ( | 85 | return ( |
| 58 | <header className="header"> | 86 | <header className="header"> |
| 59 | <div className="header-left"> | 87 | <div className="header-left"> |
| @@ -88,21 +116,44 @@ export function Header({ | |||
| 88 | 116 | ||
| 89 | {selectedProject && ( | 117 | {selectedProject && ( |
| 90 | <> | 118 | <> |
| 91 | <select | 119 | {isRenamingSession ? ( |
| 92 | value={selectedSession?.id || ""} | 120 | <input |
| 93 | onChange={(e) => { | 121 | autoFocus |
| 94 | const session = sessions.find((s) => s.id === e.target.value); | 122 | value={renameValue} |
| 95 | onSelectSession(session || null); | 123 | onChange={(e) => setRenameValue(e.target.value)} |
| 96 | }} | 124 | onKeyDown={(e) => { |
| 97 | > | 125 | if (e.key === "Enter") commitRename(); |
| 98 | <option value="">Select Session...</option> | 126 | if (e.key === "Escape") cancelRename(); |
| 99 | {sessions.map((s) => ( | 127 | }} |
| 100 | <option key={s.id} value={s.id}> | 128 | onBlur={commitRename} |
| 101 | {s.name} | 129 | className="session-rename-input" |
| 102 | </option> | 130 | /> |
| 103 | ))} | 131 | ) : ( |
| 104 | </select> | 132 | <select |
| 133 | value={selectedSession?.id || ""} | ||
| 134 | onChange={(e) => { | ||
| 135 | const session = sessions.find((s) => s.id === e.target.value); | ||
| 136 | onSelectSession(session || null); | ||
| 137 | }} | ||
| 138 | > | ||
| 139 | <option value="">Select Session...</option> | ||
| 140 | {sessions.map((s) => ( | ||
| 141 | <option key={s.id} value={s.id}> | ||
| 142 | {s.name} | ||
| 143 | </option> | ||
| 144 | ))} | ||
| 145 | </select> | ||
| 146 | )} | ||
| 105 | <button onClick={onCreateSession}>+ Session</button> | 147 | <button onClick={onCreateSession}>+ Session</button> |
| 148 | {selectedSession && onRenameSession && !isRenamingSession && ( | ||
| 149 | <button | ||
| 150 | onClick={startRename} | ||
| 151 | className="btn-rename" | ||
| 152 | title="Rename session" | ||
| 153 | > | ||
| 154 | ✏️ | ||
| 155 | </button> | ||
| 156 | )} | ||
| 106 | {selectedSession && onDeleteSession && ( | 157 | {selectedSession && onDeleteSession && ( |
| 107 | <button | 158 | <button |
| 108 | onClick={handleDeleteSession} | 159 | onClick={handleDeleteSession} |
