refactor: replace GlobalSnackbar with sonner toast

Remove the custom 373-line GlobalSnackbar component and its Provider/Hook
in favor of the already-installed sonner library.

Changes:
- Delete src/components/GlobalSnackbar.tsx (+ tests)
- Remove SnackbarProvider from popup/App.tsx and sidepanel/App.tsx
- Replace useSnackbar() calls with toast.success()/toast.error() in:
  - ImageUploader.tsx
  - useQrCode.ts
  - ParsePanel.tsx
  - LiveClock.tsx
- Update test mocks to use sonner instead of GlobalSnackbar

-554 lines, +43 lines (net -511 lines)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
雨霖铃
2026-05-29 20:47:16 +08:00
parent c2e54b34cb
commit 2727e60025
10 changed files with 43 additions and 556 deletions
+5 -6
View File
@@ -1,7 +1,7 @@
import { useCallback, useEffect } from 'react';
import { toast } from 'sonner';
import TextInputArea from '@/components/TextInputArea';
import ImageUploader from '@/components/ImageUploader';
import { useSnackbar } from '@/components/GlobalSnackbar';
import { useI18n } from '@/utils/chromeI18n';
import { useQrCodeContext } from '../contexts/QrCodeContext';
import { Label } from '@/components/ui/label';
@@ -9,7 +9,6 @@ import { cn } from '@/lib/utils';
export default function ParsePanel() {
const { t } = useI18n('qrCode');
const { showMessage } = useSnackbar();
const { parserState, setParserState, handleFileChange, handleClearFile } = useQrCodeContext();
// 全局粘贴事件监听
@@ -24,7 +23,7 @@ export default function ParsePanel() {
const file = items[i].getAsFile();
if (file) {
handleFileChange(file);
showMessage(t('qrCode:imagePasted'), { severity: 'success', autoHideDuration: 1000 });
toast.success(t('qrCode:imagePasted'));
}
return;
}
@@ -38,14 +37,14 @@ export default function ParsePanel() {
const blob = await response.blob();
const file = new File([blob], 'pasted-image.png', { type: blob.type });
handleFileChange(file);
showMessage(t('qrCode:imagePasted'), { severity: 'success', autoHideDuration: 1000 });
toast.success(t('qrCode:imagePasted'));
} catch (error) {
console.error('处理 Base64 图片失败:', error);
showMessage(t('qrCode:imagePasteError'), { severity: 'error', autoHideDuration: 3000 });
toast.error(t('qrCode:imagePasteError'));
}
}
},
[handleFileChange, showMessage, t],
[handleFileChange, t],
);
useEffect(() => {
+10 -11
View File
@@ -1,6 +1,6 @@
import { useCallback, useMemo, useState } from 'react';
import QRious from 'qrious';
import { useSnackbar } from '@/components/GlobalSnackbar';
import { toast } from 'sonner';
import { parseQrCodeFromFile } from '@/utils/qrCodeParser';
import { useContextMenuData } from '@/utils/useContextMenuData';
import { useI18n } from '@/utils/chromeI18n';
@@ -10,7 +10,6 @@ import type { QrCodeGeneratorState, QrCodeMode, QrCodeParserState } from '../typ
export function useQrCode(): QrCodeContextValue {
const { t } = useI18n('qrCode');
const { showMessage } = useSnackbar();
const [mode, setMode] = useState<QrCodeMode>('generate');
@@ -89,22 +88,22 @@ export function useQrCode(): QrCodeContextValue {
if (result.success && result.data) {
setParserState((prev) => ({ ...prev, decodedResult: result.data! }));
showMessage(t('qrCode:parseSuccess'), { severity: 'success', autoHideDuration: 1000 });
toast.success(t('qrCode:parseSuccess'));
} else {
const errorMsg = result.error || t('qrCode:noQrDetected');
setParserState((prev) => ({ ...prev, parseError: errorMsg }));
showMessage(errorMsg, { severity: 'error', autoHideDuration: 3000 });
toast.error(errorMsg);
}
} catch (error) {
console.error('解析二维码失败:', error);
const errorMsg = error instanceof Error ? error.message : t('qrCode:parseError');
setParserState((prev) => ({ ...prev, parseError: errorMsg }));
showMessage(errorMsg, { severity: 'error', autoHideDuration: 3000 });
toast.error(errorMsg);
} finally {
setParserState((prev) => ({ ...prev, parsing: false }));
}
},
[t, showMessage],
[t],
);
const downloadQrCode = useCallback(() => {
@@ -114,8 +113,8 @@ export function useQrCode(): QrCodeContextValue {
link.href = qrCodeDataUrl;
link.download = 'qrcode.png';
link.click();
showMessage(t('qrCode:qrCodeDownloadSuccess'), { severity: 'success', autoHideDuration: 1000 });
}, [qrCodeDataUrl, showMessage, t]);
toast.success(t('qrCode:qrCodeDownloadSuccess'));
}, [qrCodeDataUrl, t]);
const copyQrCode = useCallback(async () => {
if (!qrCodeDataUrl) return;
@@ -130,12 +129,12 @@ export function useQrCode(): QrCodeContextValue {
}),
]);
showMessage(t('qrCode:qrCodeCopySuccess'), { severity: 'success', autoHideDuration: 1000 });
toast.success(t('qrCode:qrCodeCopySuccess'));
} catch (error) {
console.error('复制二维码失败:', error);
showMessage(t('qrCode:copyError'), { severity: 'error', autoHideDuration: 3000 });
toast.error(t('qrCode:copyError'));
}
}, [qrCodeDataUrl, showMessage, t]);
}, [qrCodeDataUrl, t]);
const handleFileChange = useCallback(
(file: File) => {