Prevent mui card ripple on child element click
This commit is contained in:
26
src/lib/mui/MUI.util.ts
Normal file
26
src/lib/mui/MUI.util.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { BaseSyntheticEvent } from 'react';
|
||||||
|
import { chainEventHandlers } from 'material-ui-popup-state/chainEventHandlers';
|
||||||
|
|
||||||
|
export class MUIUtil {
|
||||||
|
static preventRipple(): (e: BaseSyntheticEvent) => void {
|
||||||
|
return (e) => e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
static preventRippleProp<T extends Record<string, unknown>[]>(
|
||||||
|
...handlers: T
|
||||||
|
): T & Pick<React.DOMAttributes<unknown>, 'onMouseDown' | 'onTouchStart'> {
|
||||||
|
// @ts-ignore - "chainEventHandlers" is wrongly typed
|
||||||
|
return chainEventHandlers(...handlers, {
|
||||||
|
onMouseDown: MUIUtil.preventRipple(),
|
||||||
|
onTouchStart: MUIUtil.preventRipple(),
|
||||||
|
}) as T & Pick<React.DOMAttributes<unknown>, 'onMouseDown' | 'onTouchStart'>;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import { ChapterIdInfo, Chapters } from '@/modules/chapter/services/Chapters.ts'
|
|||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import { makeToast } from '@/modules/core/utils/Toast.ts';
|
import { makeToast } from '@/modules/core/utils/Toast.ts';
|
||||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||||
|
import { MUIUtil } from '@/lib/mui/MUI.util.ts';
|
||||||
|
|
||||||
export const ChapterDownloadButton = ({
|
export const ChapterDownloadButton = ({
|
||||||
chapterId,
|
chapterId,
|
||||||
@@ -40,6 +41,7 @@ export const ChapterDownloadButton = ({
|
|||||||
return (
|
return (
|
||||||
<CustomTooltip title={t('chapter.action.download.add.label.action')}>
|
<CustomTooltip title={t('chapter.action.download.add.label.action')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
{...MUIUtil.preventRippleProp()}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import {
|
|||||||
} from '@/modules/chapter/services/Chapters.ts';
|
} from '@/modules/chapter/services/Chapters.ts';
|
||||||
import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts';
|
import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts';
|
||||||
import { ChapterCardMetadata } from '@/modules/chapter/components/cards/ChapterCardMetadata.tsx';
|
import { ChapterCardMetadata } from '@/modules/chapter/components/cards/ChapterCardMetadata.tsx';
|
||||||
|
import { MUIUtil } from '@/lib/mui/MUI.util.ts';
|
||||||
|
|
||||||
type TChapter = ChapterIdInfo &
|
type TChapter = ChapterIdInfo &
|
||||||
ChapterMangaInfo &
|
ChapterMangaInfo &
|
||||||
@@ -87,11 +88,11 @@ export const ChapterCard = memo((props: IProps) => {
|
|||||||
|
|
||||||
const handleClickOpenMenu = (
|
const handleClickOpenMenu = (
|
||||||
event: React.MouseEvent | React.TouchEvent,
|
event: React.MouseEvent | React.TouchEvent,
|
||||||
openMenu: (e: React.SyntheticEvent) => void,
|
openMenu?: (e: React.SyntheticEvent) => void,
|
||||||
) => {
|
) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
openMenu(event);
|
openMenu?.(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
const longPressBind = useLongPress((event, { context: openMenu }) => {
|
const longPressBind = useLongPress((event, { context: openMenu }) => {
|
||||||
@@ -187,8 +188,9 @@ export const ChapterCard = memo((props: IProps) => {
|
|||||||
<CustomTooltip title={t('global.button.options')}>
|
<CustomTooltip title={t('global.button.options')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
ref={menuButtonRef}
|
ref={menuButtonRef}
|
||||||
{...bindTrigger(popupState)}
|
{...MUIUtil.preventRippleProp(bindTrigger(popupState), {
|
||||||
onClick={(e) => handleClickOpenMenu(e, popupState.open)}
|
onClick: (e: MouseEvent) => handleClickOpenMenu(e),
|
||||||
|
})}
|
||||||
aria-label="more"
|
aria-label="more"
|
||||||
sx={{
|
sx={{
|
||||||
color: 'inherit',
|
color: 'inherit',
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import { DownloaderState, DownloadState } from '@/lib/graphql/generated/graphql.
|
|||||||
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
|
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
|
||||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||||
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
||||||
|
import { MUIUtil } from '@/lib/mui/MUI.util.ts';
|
||||||
|
|
||||||
const HeightPreservingItem = ({ children, ...props }: BoxProps) => (
|
const HeightPreservingItem = ({ children, ...props }: BoxProps) => (
|
||||||
// the height is necessary to prevent the item container from collapsing, which confuses Virtuoso measurements
|
// the height is necessary to prevent the item container from collapsing, which confuses Virtuoso measurements
|
||||||
@@ -75,7 +76,7 @@ const DownloadChapterItem = memo(
|
|||||||
p: 1.5,
|
p: 1.5,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton sx={{ pointerEvents: 'none' }}>
|
<IconButton {...MUIUtil.preventRippleProp()} sx={{ pointerEvents: 'none' }}>
|
||||||
<DragHandle />
|
<DragHandle />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Stack sx={{ flex: 1, ml: 1 }} direction="column">
|
<Stack sx={{ flex: 1, ml: 1 }} direction="column">
|
||||||
@@ -95,6 +96,7 @@ const DownloadChapterItem = memo(
|
|||||||
{item.state === DownloadState.Error && (
|
{item.state === DownloadState.Error && (
|
||||||
<CustomTooltip title={t('global.button.retry')}>
|
<CustomTooltip title={t('global.button.retry')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
{...MUIUtil.preventRippleProp()}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -107,6 +109,7 @@ const DownloadChapterItem = memo(
|
|||||||
)}
|
)}
|
||||||
<CustomTooltip title={t('chapter.action.download.delete.label.action')}>
|
<CustomTooltip title={t('chapter.action.download.delete.label.action')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
{...MUIUtil.preventRippleProp()}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { CustomTooltip } from '@/modules/core/components/CustomTooltip.tsx';
|
import { CustomTooltip } from '@/modules/core/components/CustomTooltip.tsx';
|
||||||
import { ChapterReadInfo, Chapters, ChapterSourceOrderInfo } from '@/modules/chapter/services/Chapters.ts';
|
import { ChapterReadInfo, Chapters, ChapterSourceOrderInfo } from '@/modules/chapter/services/Chapters.ts';
|
||||||
|
import { MUIUtil } from '@/lib/mui/MUI.util.ts';
|
||||||
|
|
||||||
export const ContinueReadingButton = ({
|
export const ContinueReadingButton = ({
|
||||||
showContinueReadingButton,
|
showContinueReadingButton,
|
||||||
@@ -34,6 +35,7 @@ export const ContinueReadingButton = ({
|
|||||||
return (
|
return (
|
||||||
<CustomTooltip title={t(isFirstChapter ? 'global.button.start' : 'global.button.resume')}>
|
<CustomTooltip title={t(isFirstChapter ? 'global.button.start' : 'global.button.resume')}>
|
||||||
<Button
|
<Button
|
||||||
|
{...MUIUtil.preventRippleProp()}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
size="small"
|
size="small"
|
||||||
sx={{ minWidth: 'unset', py: 0.5, px: 0.75 }}
|
sx={{ minWidth: 'unset', py: 0.5, px: 0.75 }}
|
||||||
@@ -41,7 +43,6 @@ export const ContinueReadingButton = ({
|
|||||||
to={`${mangaLinkTo}/chapter/${chapter.sourceOrder}`}
|
to={`${mangaLinkTo}/chapter/${chapter.sourceOrder}`}
|
||||||
state={Chapters.getReaderOpenChapterLocationState(chapter)}
|
state={Chapters.getReaderOpenChapterLocationState(chapter)}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onMouseDown={(e) => e.stopPropagation()}
|
|
||||||
>
|
>
|
||||||
<PlayArrowIcon />
|
<PlayArrowIcon />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import Typography from '@mui/material/Typography';
|
|||||||
import { MangaCardMode } from '@/modules/manga/Manga.types.ts';
|
import { MangaCardMode } from '@/modules/manga/Manga.types.ts';
|
||||||
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
|
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
|
||||||
import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts';
|
import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts';
|
||||||
|
import { MUIUtil } from '@/lib/mui/MUI.util.ts';
|
||||||
|
|
||||||
const BadgeContainer = styled('div')(({ theme }) => ({
|
const BadgeContainer = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -57,7 +58,7 @@ export const MangaBadges = ({
|
|||||||
component="div"
|
component="div"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
size="small"
|
size="small"
|
||||||
onMouseDown={(e) => e.stopPropagation()}
|
{...MUIUtil.preventRippleProp()}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { BaseSyntheticEvent, MouseEvent, TouchEvent, ChangeEvent, useMemo, forwardRef, ForwardedRef } from 'react';
|
import { BaseSyntheticEvent, ChangeEvent, useMemo, forwardRef, ForwardedRef } from 'react';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Checkbox from '@mui/material/Checkbox';
|
import Checkbox from '@mui/material/Checkbox';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
@@ -17,6 +17,7 @@ import { bindTrigger } from 'material-ui-popup-state';
|
|||||||
import { CustomTooltip } from '@/modules/core/components/CustomTooltip.tsx';
|
import { CustomTooltip } from '@/modules/core/components/CustomTooltip.tsx';
|
||||||
import { SelectableCollectionReturnType } from '@/modules/collection/hooks/useSelectableCollection.ts';
|
import { SelectableCollectionReturnType } from '@/modules/collection/hooks/useSelectableCollection.ts';
|
||||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
import { MUIUtil } from '@/lib/mui/MUI.util.ts';
|
||||||
|
|
||||||
export const MangaOptionButton = forwardRef(
|
export const MangaOptionButton = forwardRef(
|
||||||
(
|
(
|
||||||
@@ -49,12 +50,6 @@ export const MangaOptionButton = forwardRef(
|
|||||||
handleSelection?.(id, isSelected);
|
handleSelection?.(id, isSelected);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClick = (e: MouseEvent | TouchEvent) => {
|
|
||||||
preventDefaultAction(e);
|
|
||||||
popupState.open(e);
|
|
||||||
bindTriggerProps.onClick(e as any);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!handleSelection) {
|
if (!handleSelection) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -67,7 +62,7 @@ export const MangaOptionButton = forwardRef(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomTooltip title={t(selected ? 'global.button.deselect' : 'global.button.select')}>
|
<CustomTooltip title={t(selected ? 'global.button.deselect' : 'global.button.select')}>
|
||||||
<Checkbox checked={selected} onMouseDown={preventDefaultAction} onChange={handleSelectionChange} />
|
<Checkbox {...MUIUtil.preventRippleProp()} checked={selected} onChange={handleSelectionChange} />
|
||||||
</CustomTooltip>
|
</CustomTooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -77,10 +72,8 @@ export const MangaOptionButton = forwardRef(
|
|||||||
<CustomTooltip title={t('global.button.options')}>
|
<CustomTooltip title={t('global.button.options')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...bindTriggerProps}
|
{...MUIUtil.preventRippleProp(bindTriggerProps, { onClick: preventDefaultAction })}
|
||||||
onClick={handleClick}
|
|
||||||
aria-label="more"
|
aria-label="more"
|
||||||
onMouseDown={preventDefaultAction}
|
|
||||||
>
|
>
|
||||||
<MoreVertIcon />
|
<MoreVertIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -92,8 +85,7 @@ export const MangaOptionButton = forwardRef(
|
|||||||
<CustomTooltip title={t('global.button.options')}>
|
<CustomTooltip title={t('global.button.options')}>
|
||||||
<Button
|
<Button
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...bindTriggerProps}
|
{...MUIUtil.preventRippleProp(bindTriggerProps, { onClick: preventDefaultAction })}
|
||||||
onClick={handleClick}
|
|
||||||
className="manga-option-button"
|
className="manga-option-button"
|
||||||
size="small"
|
size="small"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -107,7 +99,6 @@ export const MangaOptionButton = forwardRef(
|
|||||||
display: 'none',
|
display: 'none',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
onMouseDown={(e) => e.stopPropagation()}
|
|
||||||
>
|
>
|
||||||
<MoreVertIcon />
|
<MoreVertIcon />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { GetSourcesListQuery } from '@/lib/graphql/generated/graphql.ts';
|
|||||||
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
|
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
|
||||||
import { translateExtensionLanguage } from '@/modules/extension/Extensions.utils.ts';
|
import { translateExtensionLanguage } from '@/modules/extension/Extensions.utils.ts';
|
||||||
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
|
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
|
||||||
|
import { MUIUtil } from '@/lib/mui/MUI.util.ts';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
source: GetSourcesListQuery['sources']['nodes'][number];
|
source: GetSourcesListQuery['sources']['nodes'][number];
|
||||||
@@ -134,6 +135,7 @@ export const SourceCard: React.FC<IProps> = (props: IProps) => {
|
|||||||
<Stack sx={{ flexDirection: 'row', gap: 1 }}>
|
<Stack sx={{ flexDirection: 'row', gap: 1 }}>
|
||||||
{supportsLatest && (
|
{supportsLatest && (
|
||||||
<Button
|
<Button
|
||||||
|
{...MUIUtil.preventRippleProp()}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
component={Link}
|
component={Link}
|
||||||
to={AppRoutes.sources.childRoutes.browse.path(id)}
|
to={AppRoutes.sources.childRoutes.browse.path(id)}
|
||||||
@@ -144,6 +146,7 @@ export const SourceCard: React.FC<IProps> = (props: IProps) => {
|
|||||||
)}
|
)}
|
||||||
{!isMobileWidth && (
|
{!isMobileWidth && (
|
||||||
<Button
|
<Button
|
||||||
|
{...MUIUtil.preventRippleProp()}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
component={Link}
|
component={Link}
|
||||||
to={AppRoutes.sources.childRoutes.browse.path(id)}
|
to={AppRoutes.sources.childRoutes.browse.path(id)}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { Metadata } from '@/modules/core/components/Metadata.tsx';
|
|||||||
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
|
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
|
||||||
import { TTrackerManga } from '@/modules/tracker/services/Trackers.ts';
|
import { TTrackerManga } from '@/modules/tracker/services/Trackers.ts';
|
||||||
import { MANGA_COVER_ASPECT_RATIO } from '@/modules/manga/Manga.constants.ts';
|
import { MANGA_COVER_ASPECT_RATIO } from '@/modules/manga/Manga.constants.ts';
|
||||||
|
import { MUIUtil } from '@/lib/mui/MUI.util.ts';
|
||||||
|
|
||||||
const TrackerMangaCardTitle = ({ title, selected }: { title: string; selected: boolean }) => (
|
const TrackerMangaCardTitle = ({ title, selected }: { title: string; selected: boolean }) => (
|
||||||
<Stack
|
<Stack
|
||||||
@@ -75,12 +76,11 @@ const TrackerMangaCardSummary = ({ summary }: { summary: string }) => {
|
|||||||
{summary.length && showSummaryExpandButton && (
|
{summary.length && showSummaryExpandButton && (
|
||||||
<Button
|
<Button
|
||||||
component="div"
|
component="div"
|
||||||
|
{...MUIUtil.preventRippleProp()}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setIsSummaryExpanded(!isSummaryExpanded);
|
setIsSummaryExpanded(!isSummaryExpanded);
|
||||||
}}
|
}}
|
||||||
onTouchStart={(e) => e.stopPropagation()}
|
|
||||||
onMouseDown={(e) => e.stopPropagation()}
|
|
||||||
>
|
>
|
||||||
{t(isSummaryExpanded ? 'global.button.show_less' : 'global.button.show_more')}
|
{t(isSummaryExpanded ? 'global.button.show_less' : 'global.button.show_more')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -91,13 +91,13 @@ const TrackerMangaCardSummary = ({ summary }: { summary: string }) => {
|
|||||||
|
|
||||||
const TrackerMangaCardLink = ({ children, url }: { children: React.ReactNode; url: string }) => (
|
const TrackerMangaCardLink = ({ children, url }: { children: React.ReactNode; url: string }) => (
|
||||||
<Link
|
<Link
|
||||||
|
{...MUIUtil.preventRippleProp()}
|
||||||
href={url}
|
href={url}
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
underline="none"
|
underline="none"
|
||||||
color="inherit"
|
color="inherit"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onMouseDown={(e) => e.stopPropagation()}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
Reference in New Issue
Block a user