Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/components/BoostApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -215,7 +221,7 @@ function AppShell() {
<TopBar
displayName={displayName}
displayTime={formatTodayMinutes(todayMinutes)}
onEditDisplayName={() => setIsEditingName(true)}
onEditDisplayName={openNameEditor}
onOpenHistory={() => setTab("history")}
/>
<BottomNav activeTab={tab} onTabChange={setTab} />
Expand Down
19 changes: 16 additions & 3 deletions src/components/focus/GoalForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,16 +112,23 @@ function TagSelector({
const [allTags, setAllTags] = useState<Tag[]>([]);
const [isOpen, setIsOpen] = useState(false);
const [isCreating, setIsCreating] = useState(false);
const ref = useOutsideDismiss(isOpen, () => {
const createButtonRef = useRef<HTMLButtonElement>(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]);
Expand Down Expand Up @@ -170,14 +177,20 @@ function TagSelector({
) : null}
</div>
{isOpen ? (
<div className="absolute top-full left-0 mt-2 w-64 bg-gray-800 border border-gray-700 rounded-lg shadow-xl z-50 max-h-80 overflow-y-auto">
<div
className="absolute top-full left-0 mt-2 w-64 bg-gray-800 border border-gray-700 rounded-lg shadow-xl z-50 max-h-80 overflow-y-auto"
onKeyDown={(event) => {
if (event.key === "Escape" && !isCreating) close();
}}
>
<div className="p-2">
{isCreating ? (
<NewTagForm onCreate={create} onCancel={() => setIsCreating(false)} />
) : (
<>
<div className="mb-2 flex gap-1">
<button
ref={createButtonRef}
onClick={() => setIsCreating(true)}
className="flex-1 flex items-center gap-2 px-3 py-2 text-sm text-gray-300 hover:bg-gray-700 rounded transition-colors"
>
Expand Down
1 change: 1 addition & 0 deletions src/components/pages/HistoryPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,7 @@ export function HistoryPage({ userId }: { userId: string }) {
type="text"
value={editGoal}
onChange={(e) => setEditGoal(e.target.value)}
autoFocus
className="flex-1 bg-gray-800 border border-gray-700 rounded px-2 py-1 text-sm text-white focus:outline-none focus:border-blue-500"
placeholder="Session title"
/>
Expand Down
1 change: 1 addition & 0 deletions src/components/pages/goals/GoalCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export function GoalCard(props: GoalCardProps) {
type="text"
value={draft}
onChange={(e) => setDraft(e.target.value)}
autoFocus
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
Expand Down
2 changes: 1 addition & 1 deletion src/components/pages/habits/HabitCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function EditForm({ habit, onSave, onCancel }: { habit: Habit; onSave(edit: Habi
<div className="mb-4 space-y-4">
<div>
<FieldLabel>Habit Name</FieldLabel>
<input type="text" value={name} onChange={(e) => setName(e.target.value)} className={FIELD_INPUT} />
<input type="text" value={name} onChange={(e) => setName(e.target.value)} autoFocus className={FIELD_INPUT} />
</div>
<div>
<FieldLabel>Description (Optional)</FieldLabel>
Expand Down
17 changes: 16 additions & 1 deletion src/components/shell/StartMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export function StartMenu({ onNavigate }: { onNavigate(tab: Tab): void }) {
const [isSettingsOpen, setIsSettingsOpen] = useState(true);
const [isSoundsOpen, setIsSoundsOpen] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
const panelRef = useRef<HTMLDivElement>(null);
const toggleRef = useRef<HTMLButtonElement>(null);
const isMobile = useMediaQuery(MOBILE_QUERY);
// In the 400x600 popup a floating bottom-left button would cover the timer controls,
// so the menu moves into the top bar and opens downward.
Expand All @@ -65,6 +67,10 @@ export function StartMenu({ onNavigate }: { onNavigate(tab: Tab): void }) {
const fullPageCombo = useHotkeyLabel("app.fullPage");
useHotkey("app.fullPage", () => void openFullPage(), isPopup);

useEffect(() => {
if (isOpen) panelRef.current?.querySelector("button")?.focus();
}, [isOpen]);

useEffect(() => {
if (!isOpen) return;
const closeOnOutsideClick = (event: MouseEvent) => {
Expand Down Expand Up @@ -92,7 +98,15 @@ export function StartMenu({ onNavigate }: { onNavigate(tab: Tab): void }) {
isPopup ? "top-full right-0 mt-2" : "bottom-full left-0 mb-2"
}`}
>
<div className="py-2 max-h-[70vh] overflow-y-auto force-scrollbar">
<div
ref={panelRef}
className="py-2 max-h-[70vh] overflow-y-auto force-scrollbar"
onKeyDown={(event) => {
if (event.key !== "Escape") return;
setIsOpen(false);
toggleRef.current?.focus();
}}
>
{isPopup ? (
<button onClick={() => void openFullPage()} className={TOP_ITEM}>
<Expand className="w-5 h-5" />
Expand Down Expand Up @@ -132,6 +146,7 @@ export function StartMenu({ onNavigate }: { onNavigate(tab: Tab): void }) {
</div>
) : null}
<button
ref={toggleRef}
onClick={() => setIsOpen(!isOpen)}
aria-label="Menu"
title={menuCombo ? `Menu (${menuCombo})` : "Menu"}
Expand Down
Loading