Move key commands to <AppViewSwitcher />, fix auto-focus tooltip issue
This commit is contained in:
parent
8a72e3d7ce
commit
63e843e2d6
@ -24,7 +24,6 @@ import AdminUploadPanel from '@/admin/upload/AdminUploadPanel';
|
|||||||
import { revalidatePath } from 'next/cache';
|
import { revalidatePath } from 'next/cache';
|
||||||
import RecipeModal from '@/recipe/RecipeModal';
|
import RecipeModal from '@/recipe/RecipeModal';
|
||||||
import ThemeColors from '@/app/ThemeColors';
|
import ThemeColors from '@/app/ThemeColors';
|
||||||
import AppKeyListener from '@/app/AppKeyListener';
|
|
||||||
|
|
||||||
import '../tailwind.css';
|
import '../tailwind.css';
|
||||||
|
|
||||||
@ -115,7 +114,6 @@ export default function RootLayout({
|
|||||||
</SwrConfigClient>
|
</SwrConfigClient>
|
||||||
<Analytics debug={false} />
|
<Analytics debug={false} />
|
||||||
<SpeedInsights debug={false} />
|
<SpeedInsights debug={false} />
|
||||||
<AppKeyListener />
|
|
||||||
<PhotoEscapeHandler />
|
<PhotoEscapeHandler />
|
||||||
<ToasterWithThemes />
|
<ToasterWithThemes />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -12,7 +12,9 @@ import { GRID_HOMEPAGE_ENABLED } from './config';
|
|||||||
import AdminAppMenu from '@/admin/AdminAppMenu';
|
import AdminAppMenu from '@/admin/AdminAppMenu';
|
||||||
import Spinner from '@/components/Spinner';
|
import Spinner from '@/components/Spinner';
|
||||||
import clsx from 'clsx/lite';
|
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';
|
export type SwitcherSelection = 'feed' | 'grid' | 'admin';
|
||||||
|
|
||||||
@ -23,32 +25,39 @@ export default function AppViewSwitcher({
|
|||||||
currentSelection?: SwitcherSelection
|
currentSelection?: SwitcherSelection
|
||||||
className?: string
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isUserSignedIn,
|
isUserSignedIn,
|
||||||
isUserSignedInEager,
|
isUserSignedInEager,
|
||||||
setIsCommandKOpen,
|
setIsCommandKOpen,
|
||||||
} = useAppState();
|
} = useAppState();
|
||||||
|
|
||||||
const hasAdminMenuOpenedOnce = useRef(false);
|
const refHrefFeed = useRef<HTMLAnchorElement>(null);
|
||||||
const [isAdminMenuOpen, setIsAdminMenuOpen] = useState(false);
|
const refHrefGrid = useRef<HTMLAnchorElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
||||||
if (isAdminMenuOpen) {
|
switch (e.key.toLocaleUpperCase()) {
|
||||||
hasAdminMenuOpenedOnce.current = true;
|
case 'F':
|
||||||
} else if (hasAdminMenuOpenedOnce.current) {
|
if (pathname !== PATH_FEED_INFERRED) { refHrefFeed.current?.click(); }
|
||||||
// Blur admin menu button to avoid tooltip on dismiss
|
break;
|
||||||
setTimeout(() => {
|
case 'G':
|
||||||
if (document.activeElement instanceof HTMLElement) {
|
if (pathname !== PATH_GRID_INFERRED) { refHrefGrid.current?.click(); }
|
||||||
document.activeElement.blur();
|
break;
|
||||||
}
|
case 'A':
|
||||||
}, 50);
|
if (isUserSignedIn) { setIsAdminMenuOpen(true); }
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}, [isAdminMenuOpen]);
|
}, [pathname, isUserSignedIn]);
|
||||||
|
useKeydownHandler({ onKeyDown });
|
||||||
|
|
||||||
|
const [isAdminMenuOpen, setIsAdminMenuOpen] = useState(false);
|
||||||
|
|
||||||
const renderItemFeed =
|
const renderItemFeed =
|
||||||
<SwitcherItem
|
<SwitcherItem
|
||||||
icon={<IconFeed includeTitle={false} />}
|
icon={<IconFeed includeTitle={false} />}
|
||||||
href={PATH_FEED_INFERRED}
|
href={PATH_FEED_INFERRED}
|
||||||
|
hrefRef={refHrefFeed}
|
||||||
active={currentSelection === 'feed'}
|
active={currentSelection === 'feed'}
|
||||||
tooltip={{
|
tooltip={{
|
||||||
content: 'Feed',
|
content: 'Feed',
|
||||||
@ -61,6 +70,7 @@ export default function AppViewSwitcher({
|
|||||||
<SwitcherItem
|
<SwitcherItem
|
||||||
icon={<IconGrid includeTitle={false} />}
|
icon={<IconGrid includeTitle={false} />}
|
||||||
href={PATH_GRID_INFERRED}
|
href={PATH_GRID_INFERRED}
|
||||||
|
hrefRef={refHrefGrid}
|
||||||
active={currentSelection === 'grid'}
|
active={currentSelection === 'grid'}
|
||||||
tooltip={{
|
tooltip={{
|
||||||
content: 'Grid',
|
content: 'Grid',
|
||||||
@ -93,7 +103,10 @@ export default function AppViewSwitcher({
|
|||||||
isOpen={isAdminMenuOpen}
|
isOpen={isAdminMenuOpen}
|
||||||
setIsOpen={setIsAdminMenuOpen}
|
setIsOpen={setIsAdminMenuOpen}
|
||||||
/>}
|
/>}
|
||||||
tooltip={{ content: !isAdminMenuOpen ? 'Admin Menu' : undefined }}
|
tooltip={{
|
||||||
|
content: !isAdminMenuOpen ? 'Admin Menu' : undefined,
|
||||||
|
keyCommand: !isAdminMenuOpen ? 'A' : undefined,
|
||||||
|
}}
|
||||||
noPadding
|
noPadding
|
||||||
/>}
|
/>}
|
||||||
</Switcher>
|
</Switcher>
|
||||||
|
|||||||
@ -61,7 +61,10 @@ export default function Modal({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEscapeHandler(onClose, true);
|
useEscapeHandler({
|
||||||
|
onKeyDown: onClose,
|
||||||
|
ignoreShouldRespondToKeyboardCommands: true,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { clsx } from 'clsx/lite';
|
import { clsx } from 'clsx/lite';
|
||||||
import { SHOULD_PREFETCH_ALL_LINKS } from '@/app/config';
|
import { SHOULD_PREFETCH_ALL_LINKS } from '@/app/config';
|
||||||
import { ComponentProps, ReactNode } from 'react';
|
import { ComponentProps, ReactNode, RefObject } from 'react';
|
||||||
import Spinner from './Spinner';
|
import Spinner from './Spinner';
|
||||||
import LinkWithIconLoader from './LinkWithIconLoader';
|
import LinkWithIconLoader from './LinkWithIconLoader';
|
||||||
import Tooltip from './Tooltip';
|
import Tooltip from './Tooltip';
|
||||||
@ -11,6 +11,7 @@ export default function SwitcherItem({
|
|||||||
icon,
|
icon,
|
||||||
title,
|
title,
|
||||||
href,
|
href,
|
||||||
|
hrefRef,
|
||||||
className: classNameProp,
|
className: classNameProp,
|
||||||
onClick,
|
onClick,
|
||||||
active,
|
active,
|
||||||
@ -22,6 +23,7 @@ export default function SwitcherItem({
|
|||||||
icon: ReactNode
|
icon: ReactNode
|
||||||
title?: string
|
title?: string
|
||||||
href?: string
|
href?: string
|
||||||
|
hrefRef?: RefObject<HTMLAnchorElement | null>
|
||||||
className?: string
|
className?: string
|
||||||
onClick?: () => void
|
onClick?: () => void
|
||||||
active?: boolean
|
active?: boolean
|
||||||
@ -57,6 +59,7 @@ export default function SwitcherItem({
|
|||||||
const content = href
|
const content = href
|
||||||
? <LinkWithIconLoader {...{
|
? <LinkWithIconLoader {...{
|
||||||
href,
|
href,
|
||||||
|
ref: hrefRef,
|
||||||
title,
|
title,
|
||||||
className,
|
className,
|
||||||
prefetch,
|
prefetch,
|
||||||
@ -72,7 +75,7 @@ export default function SwitcherItem({
|
|||||||
? <Tooltip
|
? <Tooltip
|
||||||
{...tooltip}
|
{...tooltip}
|
||||||
classNameTrigger={WIDTH_CLASS}
|
classNameTrigger={WIDTH_CLASS}
|
||||||
delayDuration={300}
|
delayDuration={500}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@ -75,6 +75,7 @@ export default function MoreMenu({
|
|||||||
<DropdownMenu.Portal>
|
<DropdownMenu.Portal>
|
||||||
<DropdownMenu.Content
|
<DropdownMenu.Content
|
||||||
{...props}
|
{...props}
|
||||||
|
onCloseAutoFocus={e => e.preventDefault()}
|
||||||
align={align}
|
align={align}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
|
|||||||
@ -12,11 +12,11 @@ export default function PhotoEscapeHandler() {
|
|||||||
|
|
||||||
const escapePath = getEscapePath(pathname);
|
const escapePath = getEscapePath(pathname);
|
||||||
|
|
||||||
const escapeHandler = useCallback(() => {
|
const onKeyDown = useCallback(() => {
|
||||||
if (escapePath) { router.push(escapePath, { scroll: false }); }
|
if (escapePath) { router.push(escapePath, { scroll: false }); }
|
||||||
}, [escapePath, router]);
|
}, [escapePath, router]);
|
||||||
|
|
||||||
useEscapeHandler(escapeHandler);
|
useEscapeHandler({ onKeyDown });
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
import useKeydownHandler from '@/utility/useKeydownHandler';
|
import useKeydownHandler from '@/utility/useKeydownHandler';
|
||||||
|
|
||||||
export default function useEscapeHandler(
|
export default function useEscapeHandler(
|
||||||
onKeyDown?: (e: KeyboardEvent) => void,
|
args: Omit<Parameters<typeof useKeydownHandler>[0], 'keys'>,
|
||||||
ignoreShouldRespondToKeyboardCommands?: boolean,
|
|
||||||
) {
|
) {
|
||||||
useKeydownHandler({
|
useKeydownHandler({
|
||||||
onKeyDown,
|
...args,
|
||||||
keys: ['ESCAPE'],
|
keys: ['ESCAPE'],
|
||||||
ignoreShouldRespondToKeyboardCommands,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user