From 781d89a75aad8e1aa4d4b4568d52c8e05b3e4e2f Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Tue, 10 Jun 2025 22:09:02 +0200 Subject: [PATCH] Prevent native mobile context menu --- .../chapter/components/cards/ChapterCard.tsx | 5 ++++- src/modules/core/utils/MediaQuery.tsx | 22 ++++++++++++++++++- .../manga/components/cards/MangaGridCard.tsx | 8 ++++++- .../manga/components/cards/MangaListCard.tsx | 6 ++++- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/modules/chapter/components/cards/ChapterCard.tsx b/src/modules/chapter/components/cards/ChapterCard.tsx index c02893bd..ec2f964f 100644 --- a/src/modules/chapter/components/cards/ChapterCard.tsx +++ b/src/modules/chapter/components/cards/ChapterCard.tsx @@ -39,6 +39,7 @@ import { ChapterReadInfo, ChapterScanlatorInfo, } from '@/modules/chapter/Chapter.types.ts'; +import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx'; type TChapter = ChapterIdInfo & ChapterMangaInfo & @@ -62,6 +63,7 @@ interface IProps { export const ChapterCard = memo((props: IProps) => { const { t } = useTranslation(); const theme = useTheme(); + const preventMobileContextMenu = MediaQuery.usePreventMobileContextMenu(); const menuButtonRef = useRef(null); @@ -120,9 +122,10 @@ export const ChapterCard = memo((props: IProps) => { matchSystemThemeMode.removeEventListener('change', handleSystemThemeModeChange); } + + static usePreventMobileContextMenu() { + const isTouchDevice = MediaQuery.useIsTouchDevice(); + + return useCallback( + (e: React.MouseEvent) => { + if (isTouchDevice) { + e.preventDefault(); + } + }, + [isTouchDevice], + ); + } + + static preventMobileContextMenuSx(): SxProps { + return { + userSelect: 'none', + '-webkit-touch-callout': 'none', + }; + } } diff --git a/src/modules/manga/components/cards/MangaGridCard.tsx b/src/modules/manga/components/cards/MangaGridCard.tsx index a20f1c76..2d729d89 100644 --- a/src/modules/manga/components/cards/MangaGridCard.tsx +++ b/src/modules/manga/components/cards/MangaGridCard.tsx @@ -22,6 +22,7 @@ import { SpecificMangaCardProps } from '@/modules/manga/Manga.types.ts'; import { TypographyMaxLines } from '@/modules/core/components/texts/TypographyMaxLines.tsx'; import { MANGA_COVER_ASPECT_RATIO } from '@/modules/manga/Manga.constants.ts'; import { GridLayout } from '@/modules/core/Core.types.ts'; +import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx'; const BottomGradient = styled('div')({ position: 'absolute', @@ -55,6 +56,7 @@ export const MangaGridCard = memo( mangaBadges, mode, }: SpecificMangaCardProps) => { + const preventMobileContextMenu = MediaQuery.usePreventMobileContextMenu(); const optionButtonRef = useRef(null); const { id, title } = manga; @@ -66,7 +68,11 @@ export const MangaGridCard = memo( onClick={handleClick} to={mangaLinkTo} state={Mangas.createLocationState(manga, mode)} - sx={{ textDecoration: 'none', userSelect: 'none' }} + onContextMenu={preventMobileContextMenu} + sx={{ + ...MediaQuery.preventMobileContextMenuSx(), + textDecoration: 'none', + }} > { + const preventMobileContextMenu = MediaQuery.usePreventMobileContextMenu(); + const optionButtonRef = useRef(null); const { id, title } = manga; @@ -47,8 +50,9 @@ export const MangaListCard = memo( state={Mangas.createLocationState(manga, mode)} onClick={handleClick} {...longPressBind(() => popupState.open(optionButtonRef.current))} + onContextMenu={preventMobileContextMenu} sx={{ - userSelect: 'none', + ...MediaQuery.preventMobileContextMenuSx(), '@media (hover: hover) and (pointer: fine)': { '&:hover .manga-option-button': { visibility: 'visible',