Files
suwayomi-material-you-webui/src/features/core/components/feedback/SnackbarWithDescription.tsx

118 lines
5.0 KiB
TypeScript
Raw Normal View History

2024-12-20 17:24:40 +01:00
/*
* 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/.
*/
2025-01-02 03:04:14 +01:00
import { closeSnackbar, CustomContentProps, SnackbarContent, VariantType } from 'notistack';
import { ForwardedRef, forwardRef, Fragment, memo } from 'react';
2024-12-20 17:24:40 +01:00
import Alert from '@mui/material/Alert';
import AlertTitle from '@mui/material/AlertTitle';
import Button from '@mui/material/Button';
import { useTranslation } from 'react-i18next';
import { useTheme } from '@mui/material/styles';
2025-08-15 22:02:58 +02:00
import { awaitConfirmation } from '@/features/core/utils/AwaitableDialog.tsx';
2024-12-20 17:24:40 +01:00
import { TranslationKey } from '@/Base.types.ts';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
2025-08-15 22:02:58 +02:00
import { MediaQuery } from '@/features/core/utils/MediaQuery.tsx';
import { extractGraphqlExceptionInfo } from '@/lib/HelperFunctions.ts';
2024-12-20 17:24:40 +01:00
const MAX_DESCRIPTION_LENGTH = 200;
2024-12-20 17:24:40 +01:00
const SNACKBAR_VARIANT_TO_TRANSLATION_KEY: Record<VariantType, TranslationKey> = {
default: 'global.label.info',
info: 'global.label.info',
success: 'global.label.success',
warning: 'global.label.warning',
error: 'global.label.error',
};
export const SnackbarWithDescription = memo(
forwardRef(
(
{
id,
message,
description,
variant,
action,
}: CustomContentProps & {
// eslint-disable-next-line react/no-unused-prop-types
description?: string;
},
ref: ForwardedRef<HTMLDivElement>,
) => {
const { t } = useTranslation();
const theme = useTheme();
2024-12-20 17:24:40 +01:00
const severity = variant === 'default' ? 'info' : variant;
2024-12-20 17:24:40 +01:00
const finalAction = typeof action === 'function' ? action(id) : action;
const { isGraphqlException, graphqlError, graphqlStackTrace } = extractGraphqlExceptionInfo(description);
const finalDescription = isGraphqlException ? graphqlError : description;
const isDescriptionTooLong = (finalDescription?.length ?? 0) > MAX_DESCRIPTION_LENGTH;
2024-12-20 17:24:40 +01:00
const actualDescription = isDescriptionTooLong
? finalDescription?.slice(0, MAX_DESCRIPTION_LENGTH)
: finalDescription;
2024-12-20 17:24:40 +01:00
const TitleComponent = actualDescription?.length ? AlertTitle : Fragment;
2024-12-20 17:24:40 +01:00
return (
<SnackbarContent ref={ref}>
2025-01-02 03:04:14 +01:00
<Alert
elevation={1}
severity={severity}
action={finalAction}
sx={{
wordBreak: 'break-word',
minWidth: '300px',
[theme.breakpoints.down(MediaQuery.MOBILE_WIDTH)]: {
maxWidth: '100vw',
},
[theme.breakpoints.between(MediaQuery.MOBILE_WIDTH, MediaQuery.TABLET_WIDTH)]: {
maxWidth: '75vw',
},
[theme.breakpoints.up(MediaQuery.TABLET_WIDTH)]: {
maxWidth: '50vw',
},
}}
2025-01-02 03:04:14 +01:00
onClose={() => closeSnackbar(id)}
>
<TitleComponent>{message}</TitleComponent>
{actualDescription}
{isDescriptionTooLong || (isGraphqlException && graphqlStackTrace) ? (
2024-12-20 17:24:40 +01:00
<Button
onClick={() => {
awaitConfirmation({
title:
typeof message === 'string'
? message
: t(SNACKBAR_VARIANT_TO_TRANSLATION_KEY[variant]),
message: description ?? '',
actions: {
cancel: { show: false },
confirm: { title: t('global.label.close') },
},
}).catch(
defaultPromiseErrorHandler(
`SnackbarWithDescription: ${id} - ${message} - ${description}`,
2024-12-20 17:24:40 +01:00
),
);
}}
size="small"
>
{t('global.button.show_more')}
</Button>
) : (
''
)}
</Alert>
</SnackbarContent>
);
},
),
);