Move key commands to <AppViewSwitcher />, fix auto-focus tooltip issue

This commit is contained in:
Sam Becker 2025-04-25 20:04:12 -05:00
parent 8a72e3d7ce
commit 63e843e2d6
8 changed files with 42 additions and 54 deletions

View File

@ -24,7 +24,6 @@ import AdminUploadPanel from '@/admin/upload/AdminUploadPanel';
import { revalidatePath } from 'next/cache';
import RecipeModal from '@/recipe/RecipeModal';
import ThemeColors from '@/app/ThemeColors';
import AppKeyListener from '@/app/AppKeyListener';
import '../tailwind.css';
@ -115,7 +114,6 @@ export default function RootLayout({
</SwrConfigClient>
<Analytics debug={false} />
<SpeedInsights debug={false} />
<AppKeyListener />
<PhotoEscapeHandler />
<ToasterWithThemes />
</ThemeProvider>

View File

@ -1,28 +0,0 @@
'use client';
import useKeydownHandler from '@/utility/useKeydownHandler';
import { PATH_FEED_INFERRED, PATH_GRID_INFERRED } from './paths';
import { useCallback } from 'react';
import { useRouter, usePathname } from 'next/navigation';
const PATH_MAPS = {
G: PATH_GRID_INFERRED,
F: PATH_FEED_INFERRED,
};
export default function AppKeyListener() {
const pathname = usePathname();
const router = useRouter();
const onKeyDown = useCallback((e: KeyboardEvent) => {
const path = PATH_MAPS[e.key.toLocaleUpperCase() as keyof typeof PATH_MAPS];
if (path && pathname !== path) {
router.push(path);
}
}, [router, pathname]);
useKeydownHandler({ onKeyDown });
return null;
}

View File

@ -12,7 +12,9 @@ import { GRID_HOMEPAGE_ENABLED } from './config';
import AdminAppMenu from '@/admin/AdminAppMenu';
import Spinner from '@/components/Spinner';
import clsx from 'clsx/lite';
import { useState, useRef, useEffect } from 'react';
import { useCallback, useRef, useState } from 'react';
import useKeydownHandler from '@/utility/useKeydownHandler';
import { usePathname } from 'next/navigation';
export type SwitcherSelection = 'feed' | 'grid' | 'admin';
@ -23,32 +25,39 @@ export default function AppViewSwitcher({
currentSelection?: SwitcherSelection
className?: string
}) {
const pathname = usePathname();
const {
isUserSignedIn,
isUserSignedInEager,
setIsCommandKOpen,
} = useAppState();
const hasAdminMenuOpenedOnce = useRef(false);
const [isAdminMenuOpen, setIsAdminMenuOpen] = useState(false);
const refHrefFeed = useRef<HTMLAnchorElement>(null);
const refHrefGrid = useRef<HTMLAnchorElement>(null);
useEffect(() => {
if (isAdminMenuOpen) {
hasAdminMenuOpenedOnce.current = true;
} else if (hasAdminMenuOpenedOnce.current) {
// Blur admin menu button to avoid tooltip on dismiss
setTimeout(() => {
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
}, 50);
const onKeyDown = useCallback((e: KeyboardEvent) => {
switch (e.key.toLocaleUpperCase()) {
case 'F':
if (pathname !== PATH_FEED_INFERRED) { refHrefFeed.current?.click(); }
break;
case 'G':
if (pathname !== PATH_GRID_INFERRED) { refHrefGrid.current?.click(); }
break;
case 'A':
if (isUserSignedIn) { setIsAdminMenuOpen(true); }
break;
}
}, [isAdminMenuOpen]);
}, [pathname, isUserSignedIn]);
useKeydownHandler({ onKeyDown });
const [isAdminMenuOpen, setIsAdminMenuOpen] = useState(false);
const renderItemFeed =
<SwitcherItem
icon={<IconFeed includeTitle={false} />}
href={PATH_FEED_INFERRED}
hrefRef={refHrefFeed}
active={currentSelection === 'feed'}
tooltip={{
content: 'Feed',
@ -61,6 +70,7 @@ export default function AppViewSwitcher({
<SwitcherItem
icon={<IconGrid includeTitle={false} />}
href={PATH_GRID_INFERRED}
hrefRef={refHrefGrid}
active={currentSelection === 'grid'}
tooltip={{
content: 'Grid',
@ -93,7 +103,10 @@ export default function AppViewSwitcher({
isOpen={isAdminMenuOpen}
setIsOpen={setIsAdminMenuOpen}
/>}
tooltip={{ content: !isAdminMenuOpen ? 'Admin Menu' : undefined }}
tooltip={{
content: !isAdminMenuOpen ? 'Admin Menu' : undefined,
keyCommand: !isAdminMenuOpen ? 'A' : undefined,
}}
noPadding
/>}
</Switcher>

View File

@ -61,7 +61,10 @@ export default function Modal({
},
});
useEscapeHandler(onClose, true);
useEscapeHandler({
onKeyDown: onClose,
ignoreShouldRespondToKeyboardCommands: true,
});
return (
<motion.div

View File

@ -1,6 +1,6 @@
import { clsx } from 'clsx/lite';
import { SHOULD_PREFETCH_ALL_LINKS } from '@/app/config';
import { ComponentProps, ReactNode } from 'react';
import { ComponentProps, ReactNode, RefObject } from 'react';
import Spinner from './Spinner';
import LinkWithIconLoader from './LinkWithIconLoader';
import Tooltip from './Tooltip';
@ -11,6 +11,7 @@ export default function SwitcherItem({
icon,
title,
href,
hrefRef,
className: classNameProp,
onClick,
active,
@ -22,6 +23,7 @@ export default function SwitcherItem({
icon: ReactNode
title?: string
href?: string
hrefRef?: RefObject<HTMLAnchorElement | null>
className?: string
onClick?: () => void
active?: boolean
@ -57,6 +59,7 @@ export default function SwitcherItem({
const content = href
? <LinkWithIconLoader {...{
href,
ref: hrefRef,
title,
className,
prefetch,
@ -72,7 +75,7 @@ export default function SwitcherItem({
? <Tooltip
{...tooltip}
classNameTrigger={WIDTH_CLASS}
delayDuration={300}
delayDuration={500}
>
{content}
</Tooltip>

View File

@ -75,6 +75,7 @@ export default function MoreMenu({
<DropdownMenu.Portal>
<DropdownMenu.Content
{...props}
onCloseAutoFocus={e => e.preventDefault()}
align={align}
sideOffset={sideOffset}
className={clsx(

View File

@ -12,11 +12,11 @@ export default function PhotoEscapeHandler() {
const escapePath = getEscapePath(pathname);
const escapeHandler = useCallback(() => {
const onKeyDown = useCallback(() => {
if (escapePath) { router.push(escapePath, { scroll: false }); }
}, [escapePath, router]);
useEscapeHandler(escapeHandler);
useEscapeHandler({ onKeyDown });
return null;
}

View File

@ -1,12 +1,10 @@
import useKeydownHandler from '@/utility/useKeydownHandler';
export default function useEscapeHandler(
onKeyDown?: (e: KeyboardEvent) => void,
ignoreShouldRespondToKeyboardCommands?: boolean,
args: Omit<Parameters<typeof useKeydownHandler>[0], 'keys'>,
) {
useKeydownHandler({
onKeyDown,
...args,
keys: ['ESCAPE'],
ignoreShouldRespondToKeyboardCommands,
});
}