From 3e7b106de4fec3ce6b2cb04356f709c45196d76c Mon Sep 17 00:00:00 2001 From: Truong Trung Nghia <149591043+ttncode@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:48:50 +0700 Subject: [PATCH] fix: focus the right field when a form, popover or menu opens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Whatever opens now puts keyboard focus where you'd type next. | Opens | Before | Now | | --- | --- | --- | | Edit Display Name (Alt+Shift+N / Welcome menu) | focus stayed on the page | name field focused, prefilled with the current name and selected | | Habit → Edit | stayed on the page | name field focused | | Goal → comments | stayed on the page | comment field focused | | Focus summary → edit session | stayed on the page | title field focused | | Add Tag popover (Alt+Shift+T / click) | stayed on the page | "Create new tag" focused; Esc closes | | Menu (Alt+M / click) | stayed on the button | first item focused; Esc closes and returns focus to the button | Already correct and unchanged: Alt+G (goal), Alt+N on To-Dos/Goals/Habits/Tags/Boost, to-do inline edits, habit notes, new tags, dialogs and panels. ## Testing - Lint, typecheck, unit tests and build pass. - In the browser: Alt+Shift+N → name field focused with "Alex" selected, Esc closes; Alt+Shift+T → "Create new tag" focused, Esc closes; Alt+M → first menu item focused, Esc closes; Alt+G → goal field focused. --- src/components/BoostApp.tsx | 10 ++++++++-- src/components/focus/GoalForm.tsx | 19 ++++++++++++++++--- src/components/pages/HistoryPage.tsx | 1 + src/components/pages/goals/GoalCard.tsx | 1 + src/components/pages/habits/HabitCard.tsx | 2 +- src/components/shell/StartMenu.tsx | 17 ++++++++++++++++- 6 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/components/BoostApp.tsx b/src/components/BoostApp.tsx index 601daa5..d66753c 100644 --- a/src/components/BoostApp.tsx +++ b/src/components/BoostApp.tsx @@ -77,7 +77,11 @@ function AppShell() { const goBack = () => setTab(previousTab); useNavigationHotkeys(navigateFromCurrent); - useHotkey("app.displayName", () => setIsEditingName(true)); + const openNameEditor = () => { + setNewName(displayName); + setIsEditingName(true); + }; + useHotkey("app.displayName", openNameEditor); const saveName = () => { const trimmed = newName.trim(); @@ -156,6 +160,8 @@ function AppShell() { type="text" value={newName} onChange={(event) => setNewName(event.target.value)} + autoFocus + onFocus={(event) => event.currentTarget.select()} placeholder="Enter display name" className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 mb-4" onKeyDown={(event) => { @@ -215,7 +221,7 @@ function AppShell() { setIsEditingName(true)} + onEditDisplayName={openNameEditor} onOpenHistory={() => setTab("history")} /> diff --git a/src/components/focus/GoalForm.tsx b/src/components/focus/GoalForm.tsx index 5bb6dc0..8f12a1e 100644 --- a/src/components/focus/GoalForm.tsx +++ b/src/components/focus/GoalForm.tsx @@ -112,16 +112,23 @@ function TagSelector({ const [allTags, setAllTags] = useState([]); const [isOpen, setIsOpen] = useState(false); const [isCreating, setIsCreating] = useState(false); - const ref = useOutsideDismiss(isOpen, () => { + const createButtonRef = useRef(null); + const close = () => { setIsOpen(false); setIsCreating(false); - }); + }; + const ref = useOutsideDismiss(isOpen, close); const addTagCombo = useHotkeyLabel("timer.addTag"); // The Tags row stays mounted but hidden below the `wide` breakpoint. useHotkey("timer.addTag", () => { if (ref.current?.getClientRects().length) setIsOpen(true); }, isActive); + // Opening (by click or Alt+Shift+T) puts focus on the first action so the keyboard can carry on. + useEffect(() => { + if (isOpen && !isCreating) createButtonRef.current?.focus(); + }, [isOpen, isCreating]); + useEffect(() => { if (isOpen) databaseService.getTags().then(setAllTags).catch((error: unknown) => console.error("Failed to load tags:", error)); }, [isOpen]); @@ -170,7 +177,12 @@ function TagSelector({ ) : null} {isOpen ? ( -
+
{ + if (event.key === "Escape" && !isCreating) close(); + }} + >
{isCreating ? ( setIsCreating(false)} /> @@ -178,6 +190,7 @@ function TagSelector({ <>
) : null}