fix(layout): show focus ring only for keyboard navigation (#2046)

This commit is contained in:
Huang Xin
2025-09-16 03:13:52 +08:00
committed by GitHub
parent 34f1af727f
commit e9a152683e
2 changed files with 41 additions and 4 deletions
@@ -1,5 +1,5 @@
import clsx from 'clsx';
import { useCallback } from 'react';
import { useCallback, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { navigateToLibrary, navigateToReader, showReaderWindow } from '@/utils/nav';
import { useEnv } from '@/context/EnvContext';
@@ -105,6 +105,8 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
const { settings } = useSettingsStore();
const { updateBook } = useLibraryStore();
const [keyboardFocused, setKeyboardFocused] = useState(false);
const showBookDetailsModal = useCallback(async (book: Book) => {
if (await makeBookAvailable(book)) {
handleShowDetailsBook(book);
@@ -293,7 +295,7 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
[itemSelected],
);
const { pressing, handlers } = useLongPress(
const { pressing, hasPointerEventsRef, handlers } = useLongPress(
{
onLongPress: () => {
handleSelectItem();
@@ -323,6 +325,16 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
}
};
const handleFocus = () => {
if (!hasPointerEventsRef.current) {
setKeyboardFocused(true);
}
};
const handleBlur = () => {
setKeyboardFocused(false);
};
return (
<div className={clsx(mode === 'list' && 'sm:hover:bg-base-300/50 px-4 sm:px-6')}>
<div
@@ -330,7 +342,7 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
'group',
mode === 'grid' && 'sm:hover:bg-base-300/50 flex h-full flex-col px-0 py-4 sm:px-4',
mode === 'list' && 'border-base-300 flex flex-col border-b py-2',
'focus-inset-2',
keyboardFocused && 'focus-inset-2',
appService?.isMobileApp && 'no-context-menu',
pressing && mode === 'grid' ? 'scale-95' : 'scale-100',
)}
@@ -341,6 +353,8 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
transition: 'transform 0.2s',
}}
onKeyDown={handleKeyDown}
onFocus={handleFocus}
onBlur={handleBlur}
{...handlers}
>
<div className='flex-grow'>
+24 -1
View File
@@ -11,12 +11,14 @@ interface UseLongPressOptions {
interface UseLongPressResult {
pressing: boolean;
hasPointerEventsRef: React.MutableRefObject<boolean>;
handlers: {
onPointerDown: (e: React.PointerEvent) => void;
onPointerUp: (e: React.PointerEvent) => void;
onPointerMove: (e: React.PointerEvent) => void;
onPointerCancel: (e: React.PointerEvent) => void;
onPointerLeave: (e: React.PointerEvent) => void;
onClick: (e: React.MouseEvent) => void;
onContextMenu: (e: React.MouseEvent) => void;
};
}
@@ -33,9 +35,11 @@ export const useLongPress = (
deps: React.DependencyList,
): UseLongPressResult => {
const [pressing, setPressing] = useState(false);
const timerRef = useRef<NodeJS.Timeout>();
const timerRef = useRef<ReturnType<typeof setTimeout>>();
const startPosRef = useRef<{ x: number; y: number } | null>(null);
const pointerId = useRef<number | null>(null);
const hasPointerEventsRef = useRef(false);
const pointerEventTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
const isLongPressTriggered = useRef(false);
const reset = useCallback(() => {
@@ -52,6 +56,9 @@ export const useLongPress = (
return;
}
hasPointerEventsRef.current = true;
clearTimeout(pointerEventTimeoutRef.current);
pointerId.current = e.pointerId;
startPosRef.current = { x: e.clientX, y: e.clientY };
isLongPressTriggered.current = false;
@@ -97,6 +104,10 @@ export const useLongPress = (
}
reset();
pointerEventTimeoutRef.current = setTimeout(() => {
hasPointerEventsRef.current = false;
}, 100);
},
[onTap, moveThreshold, reset],
);
@@ -106,10 +117,20 @@ export const useLongPress = (
if (e.pointerId !== pointerId.current) return;
onCancel?.();
reset();
pointerEventTimeoutRef.current = setTimeout(() => {
hasPointerEventsRef.current = false;
}, 100);
},
[onCancel, reset],
);
const handleClick = useCallback(() => {
if (!hasPointerEventsRef.current) {
onTap?.();
}
}, [onTap]);
const handleContextMenu = useCallback(
(e: React.MouseEvent) => {
if (onContextMenu) {
@@ -131,12 +152,14 @@ export const useLongPress = (
return {
pressing,
hasPointerEventsRef,
handlers: {
onPointerDown: handlePointerDown,
onPointerUp: handlePointerUp,
onPointerMove: handlePointerMove,
onPointerCancel: handleCancel,
onPointerLeave: handleCancel,
onClick: handleClick,
onContextMenu: handleContextMenu,
},
};