fix: 修复暗黑模式样式兼容性
- 在 tailwind.config.js 中启用 darkMode: 'class' 并扩展语义化颜色变量 - 将全项目硬编码颜色(bg-white、text-gray-*、border-gray-* 等)替换为语义化类名 - 修复 popup/index.html 硬编码浅色背景色 - 同步更新相关单元测试中的类名断言
This commit is contained in:
@@ -73,15 +73,15 @@ export const CopyButton: React.FC<CopyButtonProps> = ({
|
||||
const iconSize = size === 'small' ? 14 : size === 'medium' ? 16 : 18;
|
||||
|
||||
const colorClasses: Record<string, string> = {
|
||||
primary: 'text-blue-600 hover:bg-blue-50',
|
||||
secondary: 'text-gray-600 hover:bg-gray-50',
|
||||
success: 'text-green-600 hover:bg-green-50',
|
||||
error: 'text-red-600 hover:bg-red-50',
|
||||
info: 'text-blue-600 hover:bg-blue-50',
|
||||
primary: 'text-primary hover:bg-primary/10',
|
||||
secondary: 'text-foreground hover:bg-muted',
|
||||
success: 'text-green-600 hover:bg-green-500/10',
|
||||
error: 'text-red-600 hover:bg-red-500/10',
|
||||
info: 'text-primary hover:bg-primary/10',
|
||||
warning: 'text-amber-600 hover:bg-amber-50',
|
||||
};
|
||||
|
||||
const colorClass = colorClasses[color] || `text-[${color}] hover:bg-gray-50`;
|
||||
const colorClass = colorClasses[color] || `text-[${color}] hover:bg-muted`;
|
||||
|
||||
return (
|
||||
<button
|
||||
@@ -91,7 +91,7 @@ export const CopyButton: React.FC<CopyButtonProps> = ({
|
||||
style={style}
|
||||
className={`${sizeClasses[size]} rounded-md flex items-center justify-center transition-all ${
|
||||
copied ? 'text-green-600 bg-green-50' : colorClass
|
||||
} bg-white shadow-sm hover:shadow-md`}
|
||||
} bg-background shadow-sm hover:shadow-md`}
|
||||
>
|
||||
{copied ? (
|
||||
<Check style={{ width: iconSize, height: iconSize }} />
|
||||
|
||||
@@ -41,33 +41,33 @@ export default function DecodeResultPaper({
|
||||
const { t } = useTranslation('base64Converter');
|
||||
|
||||
return (
|
||||
<div className="p-4 rounded-xl bg-blue-50 border border-blue-200">
|
||||
<div className="p-4 rounded-xl bg-primary/10 border border-primary/30">
|
||||
{/* 标题 */}
|
||||
<span className="block mb-2 text-xs font-bold text-gray-500">{title}</span>
|
||||
<span className="block mb-2 text-xs font-bold text-muted-foreground">{title}</span>
|
||||
|
||||
{/* 可选预览内容(ImageMode 的图片) */}
|
||||
{children}
|
||||
|
||||
{/* 文件信息 */}
|
||||
<div className="flex gap-4 mb-3">
|
||||
<span className="text-xs text-gray-400">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('inferredMimeType')}: {mimeType}
|
||||
</span>
|
||||
<span className="text-xs text-gray-400">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('decodedSize')}: {formatFileSize(blobSize)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 文件名输入 */}
|
||||
<div className="mb-3">
|
||||
<label className="block text-xs font-medium text-gray-500 mb-1">
|
||||
<label className="block text-xs font-medium text-muted-foreground mb-1">
|
||||
{t('decodedFileName')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={fileName}
|
||||
onChange={(e) => onFileNameChange(e.target.value)}
|
||||
className="w-full px-3 py-2 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -45,11 +45,11 @@ export class ErrorBoundary extends Component<Props, State> {
|
||||
<div className="p-6 text-center rounded-xl border border-red-200 bg-red-50">
|
||||
<AlertCircle className="h-16 w-16 text-red-500 mx-auto mb-4" />
|
||||
<h2 className="text-xl font-extrabold text-red-600 mb-2">糟糕,出了点问题</h2>
|
||||
<p className="text-sm text-gray-500 mb-6">
|
||||
<p className="text-sm text-muted-foreground mb-6">
|
||||
应用遇到了一些意外错误。您可以尝试刷新页面或重置应用。
|
||||
</p>
|
||||
{this.state.error && (
|
||||
<div className="mb-6 p-4 bg-gray-50 rounded-lg text-left max-h-[200px] overflow-auto">
|
||||
<div className="mb-6 p-4 bg-muted rounded-lg text-left max-h-[200px] overflow-auto">
|
||||
<pre className="font-mono text-xs whitespace-pre-wrap break-all text-red-700">
|
||||
{this.state.error.toString()}
|
||||
</pre>
|
||||
|
||||
@@ -137,7 +137,7 @@ const severityConfig: Record<
|
||||
{ icon: React.ElementType; bgClass: string; textClass: string }
|
||||
> = {
|
||||
success: { icon: CheckCircle, bgClass: 'bg-green-500', textClass: 'text-white' },
|
||||
info: { icon: Info, bgClass: 'bg-blue-500', textClass: 'text-white' },
|
||||
info: { icon: Info, bgClass: 'bg-primary/100', textClass: 'text-white' },
|
||||
warning: { icon: AlertTriangle, bgClass: 'bg-amber-500', textClass: 'text-white' },
|
||||
error: { icon: XCircle, bgClass: 'bg-red-500', textClass: 'text-white' },
|
||||
};
|
||||
|
||||
@@ -118,7 +118,7 @@ const ImageUploader = ({
|
||||
? 'border-green-600 bg-green-50'
|
||||
: selectedFile
|
||||
? 'border-green-600 bg-green-50/50'
|
||||
: 'border-gray-300 bg-gray-50 hover:border-green-600 hover:bg-green-50/50'
|
||||
: 'border-input bg-muted hover:border-green-600 hover:bg-green-500/10/50'
|
||||
}`}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
@@ -153,14 +153,21 @@ const ImageUploader = ({
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
<span className="block text-sm text-gray-500 mt-2">{selectedFile.name}</span>
|
||||
<span className="block text-xs text-gray-400">{t('qrCode:clickToChange')}</span>
|
||||
<span className="block text-sm text-muted-foreground mt-2">{selectedFile.name}</span>
|
||||
<span className="block text-xs text-muted-foreground">{t('qrCode:clickToChange')}</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Image data-testid="ImageIcon" className="w-12 h-12 text-gray-400 mx-auto mb-2" />
|
||||
<span className="block text-sm text-gray-500 mb-1">{t('qrCode:clickToUpload')}</span>
|
||||
<span className="block text-xs text-gray-400">{t('qrCode:supportFormats')}</span>
|
||||
<Image
|
||||
data-testid="ImageIcon"
|
||||
className="w-12 h-12 text-muted-foreground mx-auto mb-2"
|
||||
/>
|
||||
<span className="block text-sm text-muted-foreground mb-1">
|
||||
{t('qrCode:clickToUpload')}
|
||||
</span>
|
||||
<span className="block text-xs text-muted-foreground">
|
||||
{t('qrCode:supportFormats')}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</label>
|
||||
|
||||
@@ -47,11 +47,11 @@ export class PageErrorBoundary extends Component<Props, State> {
|
||||
<div className="p-6 text-center rounded-xl border border-red-200 bg-red-50 max-w-md w-full">
|
||||
<AlertCircle className="h-12 w-12 text-red-500 mx-auto mb-3" />
|
||||
<h3 className="text-lg font-bold text-red-600 mb-2">该页面加载失败</h3>
|
||||
<p className="text-sm text-gray-500 mb-4">
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
页面在加载或渲染时遇到错误,您可以重试或切换到其他工具。
|
||||
</p>
|
||||
{this.state.error && (
|
||||
<div className="mb-4 p-3 bg-gray-50 rounded-lg text-left max-h-[160px] overflow-auto">
|
||||
<div className="mb-4 p-3 bg-muted rounded-lg text-left max-h-[160px] overflow-auto">
|
||||
<pre className="font-mono text-xs whitespace-pre-wrap break-all text-red-700">
|
||||
{this.state.error.toString()}
|
||||
</pre>
|
||||
|
||||
@@ -92,7 +92,7 @@ export default function PageHeader({
|
||||
</div>
|
||||
{/* 副标题 */}
|
||||
{subtitle && (
|
||||
<span className="text-xs font-semibold text-gray-500" style={subtitleSx}>
|
||||
<span className="text-xs font-semibold text-muted-foreground" style={subtitleSx}>
|
||||
{subtitle}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -14,7 +14,7 @@ interface PageSkeletonProps {
|
||||
*/
|
||||
function DashboardCardSkeleton() {
|
||||
return (
|
||||
<div className="rounded-xl border border-gray-200 p-5 h-[100px]">
|
||||
<div className="rounded-xl border border-border p-5 h-[100px]">
|
||||
<div className="flex justify-between items-start">
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="w-10 h-10 rounded-lg bg-gray-200 animate-pulse" />
|
||||
|
||||
@@ -15,21 +15,23 @@ const QrCodePreview = ({ qrCodeDataUrl, onDownload, onCopy }: QrCodePreviewProps
|
||||
|
||||
if (!qrCodeDataUrl) {
|
||||
return (
|
||||
<div className="flex flex-col justify-center items-center min-h-[200px] border-2 border-dashed border-gray-300 rounded-xl p-4 bg-gray-50">
|
||||
<span className="text-sm text-gray-500 text-center">{t('qrCode:qrCodeWillShow')}</span>
|
||||
<div className="flex flex-col justify-center items-center min-h-[200px] border-2 border-dashed border-input rounded-xl p-4 bg-muted">
|
||||
<span className="text-sm text-muted-foreground text-center">
|
||||
{t('qrCode:qrCodeWillShow')}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-center items-center min-h-[200px] border-2 border-dashed border-gray-300 rounded-xl p-4 bg-gray-50">
|
||||
<div className="flex flex-col justify-center items-center min-h-[200px] border-2 border-dashed border-input rounded-xl p-4 bg-muted">
|
||||
<div className="flex flex-col items-center w-full">
|
||||
<img src={qrCodeDataUrl} alt="QR Code" className="w-[250px] h-[250px] block" />
|
||||
<div className="flex gap-2 mt-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onDownload}
|
||||
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-green-700 bg-white border border-green-600 rounded-lg hover:bg-green-50 transition-colors"
|
||||
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-green-600 bg-background border border-green-600 rounded-lg hover:bg-green-500/10 transition-colors"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
{t('qrCode:downloadButton')}
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function SwitchButtonGroup<T extends string | number = string>({
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-full mb-4 rounded-xl border border-gray-200 bg-gray-50 p-1.5 flex gap-1"
|
||||
className="w-full mb-4 rounded-xl border border-border bg-muted p-1.5 flex gap-1"
|
||||
style={sx}
|
||||
>
|
||||
{options.map((option) => (
|
||||
@@ -40,8 +40,8 @@ export default function SwitchButtonGroup<T extends string | number = string>({
|
||||
sizeClasses[size]
|
||||
} ${
|
||||
value === option.value
|
||||
? 'bg-white text-blue-600 shadow-sm'
|
||||
: 'text-gray-500 hover:text-gray-700 hover:bg-white/50'
|
||||
? 'bg-background text-primary shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-background/50'
|
||||
}`}
|
||||
style={buttonSx}
|
||||
>
|
||||
|
||||
@@ -152,9 +152,9 @@ function ActionButton({
|
||||
typeof action.disabled === 'function' ? action.disabled(value) : action.disabled || !value;
|
||||
|
||||
const typeClasses: Record<string, string> = {
|
||||
primary: 'bg-blue-600 text-white hover:bg-blue-700',
|
||||
danger: 'text-red-600 hover:bg-red-50',
|
||||
default: 'text-gray-500 hover:bg-gray-50',
|
||||
primary: 'bg-primary text-white hover:bg-primary/90',
|
||||
danger: 'text-red-600 hover:bg-red-500/10',
|
||||
default: 'text-muted-foreground hover:bg-muted',
|
||||
};
|
||||
|
||||
const sizeClasses = {
|
||||
@@ -333,7 +333,7 @@ const TextInputArea = forwardRef<HTMLTextAreaElement, TextInputAreaProps>((props
|
||||
{hasTopBar && (
|
||||
<div className="flex items-center justify-between mb-2 px-1">
|
||||
<div className="flex items-center gap-3">
|
||||
{title && <span className="text-sm font-semibold text-gray-500">{title}</span>}
|
||||
{title && <span className="text-sm font-semibold text-muted-foreground">{title}</span>}
|
||||
{topExtra}
|
||||
</div>
|
||||
|
||||
@@ -349,7 +349,7 @@ const TextInputArea = forwardRef<HTMLTextAreaElement, TextInputAreaProps>((props
|
||||
/>
|
||||
))}
|
||||
{showCount && (
|
||||
<span className="text-xs text-gray-400 tabular-nums ml-1">
|
||||
<span className="text-xs text-muted-foreground tabular-nums ml-1">
|
||||
{value.length}
|
||||
{maxLength ? ` / ${maxLength}` : ''}
|
||||
</span>
|
||||
@@ -374,10 +374,10 @@ const TextInputArea = forwardRef<HTMLTextAreaElement, TextInputAreaProps>((props
|
||||
maxHeight: autoResize ? `${maxRows * 1.5}rem` : undefined,
|
||||
}}
|
||||
className={`w-full rounded-lg border ${
|
||||
displayError ? 'border-red-300' : 'border-gray-200'
|
||||
} bg-white px-3 py-3 font-mono text-sm leading-relaxed transition-all resize-y ${
|
||||
displayError ? 'border-red-300' : 'border-border'
|
||||
} bg-background px-3 py-3 font-mono text-sm leading-relaxed transition-all resize-y ${
|
||||
showClear || allowCopy || bottomActions.length > 0 ? 'pb-10' : ''
|
||||
} focus:outline-none focus:ring-2 focus:ring-blue-500 focus:bg-white hover:bg-gray-50 ${
|
||||
} focus:outline-none focus:ring-2 focus:ring-primary focus:bg-background hover:bg-muted ${
|
||||
displayError ? 'focus:ring-red-500' : ''
|
||||
}`}
|
||||
/>
|
||||
@@ -403,7 +403,7 @@ const TextInputArea = forwardRef<HTMLTextAreaElement, TextInputAreaProps>((props
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
title={t('textInputArea.copyContent')}
|
||||
className="p-1 rounded-md text-gray-400 hover:text-blue-600 hover:bg-blue-50 transition-colors"
|
||||
className="p-1 rounded-md text-muted-foreground hover:text-primary hover:bg-primary/10 transition-colors"
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -413,7 +413,7 @@ const TextInputArea = forwardRef<HTMLTextAreaElement, TextInputAreaProps>((props
|
||||
type="button"
|
||||
onClick={handleClear}
|
||||
title={t('textInputArea.clear')}
|
||||
className="p-1 rounded-md text-gray-400 hover:text-red-600 hover:bg-red-50 transition-colors"
|
||||
className="p-1 rounded-md text-muted-foreground hover:text-red-600 hover:bg-red-500/10 transition-colors"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
+20
-20
@@ -156,27 +156,27 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
|
||||
const isDashboard = currentPage === 'dashboard';
|
||||
|
||||
return (
|
||||
<div className="flex justify-between items-center px-3 sm:px-4 py-3 border-b border-gray-200 bg-white relative z-[1100]">
|
||||
<div className="flex justify-between items-center px-3 sm:px-4 py-3 border-b border-border bg-background relative z-[1100]">
|
||||
<div className="w-8 sm:w-10">
|
||||
{!isDashboard && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={goBack}
|
||||
aria-label={t('common:buttons.back')}
|
||||
className="p-1.5 rounded-md bg-gray-50 hover:bg-gray-100 transition-colors"
|
||||
className="p-1.5 rounded-md bg-muted hover:bg-accent transition-colors"
|
||||
>
|
||||
<ArrowLeft className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<span className="hidden md:block text-xs font-extrabold tracking-wider uppercase text-gray-500 ml-2">
|
||||
<span className="hidden md:block text-xs font-extrabold tracking-wider uppercase text-muted-foreground ml-2">
|
||||
{t('common:appName')}
|
||||
</span>
|
||||
|
||||
<div className="flex-1 mx-2 sm:mx-4 relative max-w-[400px]">
|
||||
<div className="relative">
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground">
|
||||
<Search className="h-4 w-4" />
|
||||
</div>
|
||||
<input
|
||||
@@ -188,7 +188,7 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
|
||||
onFocus={() => setShowResults(true)}
|
||||
onKeyDown={handleKeyDown}
|
||||
aria-label={t('common:buttons.search')}
|
||||
className="w-full pl-9 pr-8 py-1.5 text-sm rounded-lg border border-transparent bg-gray-50 focus:bg-white focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 transition-all"
|
||||
className="w-full pl-9 pr-8 py-1.5 text-sm rounded-lg border border-transparent bg-muted focus:bg-background focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
@@ -198,7 +198,7 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
|
||||
setSelectedIndex(-1);
|
||||
}}
|
||||
aria-label={t('common:buttons.clearSearch')}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 p-0.5 rounded-md text-gray-400 hover:text-gray-600"
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 p-0.5 rounded-md text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
@@ -206,7 +206,7 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
|
||||
</div>
|
||||
|
||||
{showResults && (searchQuery.trim() || displayedHistory.length > 0) && (
|
||||
<div className="absolute top-full left-0 right-0 mt-2 bg-white rounded-lg shadow-lg border border-gray-200 max-h-[300px] overflow-auto z-[1200]">
|
||||
<div className="absolute top-full left-0 right-0 mt-2 bg-popover rounded-lg shadow-lg border border-border max-h-[300px] overflow-auto z-[1200]">
|
||||
<ul role="listbox" className="py-1">
|
||||
{searchQuery.trim() ? (
|
||||
searchResults.length > 0 ? (
|
||||
@@ -217,17 +217,17 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
|
||||
aria-selected={selectedIndex === index}
|
||||
onClick={() => handleSelectFeature(feature)}
|
||||
className={`flex items-center gap-3 px-3 py-2 cursor-pointer transition-colors ${
|
||||
selectedIndex === index ? 'bg-blue-50' : 'hover:bg-gray-50'
|
||||
selectedIndex === index ? 'bg-primary/10' : 'hover:bg-muted'
|
||||
}`}
|
||||
>
|
||||
<div className="w-8 h-8 flex items-center justify-center text-gray-500">
|
||||
<div className="w-8 h-8 flex items-center justify-center text-muted-foreground">
|
||||
{feature.icon && <feature.icon className="h-5 w-5" />}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-semibold text-gray-900 truncate">
|
||||
<p className="text-sm font-semibold text-foreground truncate">
|
||||
{t(feature.labelKey)}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 truncate">
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
{t(feature.descriptionKey)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -235,13 +235,13 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
|
||||
))
|
||||
) : (
|
||||
<li className="px-4 py-6 text-center">
|
||||
<p className="text-sm text-gray-500">{t('common:buttons.noResults')}</p>
|
||||
<p className="text-sm text-muted-foreground">{t('common:buttons.noResults')}</p>
|
||||
</li>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
<li className="px-4 py-2">
|
||||
<span className="text-xs font-bold text-gray-400">
|
||||
<span className="text-xs font-bold text-muted-foreground">
|
||||
{t('common:buttons.recentSearch')}
|
||||
</span>
|
||||
</li>
|
||||
@@ -255,13 +255,13 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
|
||||
setSelectedIndex(-1);
|
||||
}}
|
||||
className={`flex items-center gap-3 px-3 py-2 cursor-pointer transition-colors ${
|
||||
selectedIndex === index ? 'bg-blue-50' : 'hover:bg-gray-50'
|
||||
selectedIndex === index ? 'bg-primary/10' : 'hover:bg-muted'
|
||||
}`}
|
||||
>
|
||||
<div className="w-8 h-8 flex items-center justify-center text-gray-400">
|
||||
<div className="w-8 h-8 flex items-center justify-center text-muted-foreground">
|
||||
<History className="h-4 w-4" />
|
||||
</div>
|
||||
<span className="text-sm text-gray-700">{item}</span>
|
||||
<span className="text-sm text-foreground">{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</>
|
||||
@@ -277,7 +277,7 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
|
||||
onClick={toggleLanguage}
|
||||
aria-label={t('common:buttons.toggleLanguage')}
|
||||
title={t('common:buttons.toggleLanguage')}
|
||||
className="p-1.5 rounded-md hover:bg-gray-100 transition-colors"
|
||||
className="p-1.5 rounded-md hover:bg-accent transition-colors"
|
||||
>
|
||||
<Globe className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -286,7 +286,7 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
|
||||
onClick={cycleThemeMode}
|
||||
aria-label={t('common:buttons.toggleTheme')}
|
||||
title={t(`common:buttons.themeMode.${mode}`)}
|
||||
className="p-1.5 rounded-md hover:bg-gray-100 transition-colors"
|
||||
className="p-1.5 rounded-md hover:bg-accent transition-colors"
|
||||
>
|
||||
<ThemeIcon className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -295,7 +295,7 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
|
||||
onClick={handleOpenInTab}
|
||||
aria-label={t('common:buttons.openInTab')}
|
||||
title={t('common:buttons.openInTab')}
|
||||
className="p-1.5 rounded-md hover:bg-gray-100 transition-colors"
|
||||
className="p-1.5 rounded-md hover:bg-accent transition-colors"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -304,7 +304,7 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
|
||||
onClick={onOpenOptions}
|
||||
aria-label={t('common:buttons.settings')}
|
||||
title={t('common:buttons.settings')}
|
||||
className="p-1.5 rounded-md hover:bg-gray-100 transition-colors"
|
||||
className="p-1.5 rounded-md hover:bg-accent transition-colors"
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
@@ -84,7 +84,7 @@ describe('PageHeader 组件系统', () => {
|
||||
|
||||
it('无副标题时不渲染副标题区域', () => {
|
||||
const { container } = render(<PageHeader icon={defaultProps.icon} title="仅标题" />);
|
||||
const subtitles = container.querySelectorAll('.text-gray-500');
|
||||
const subtitles = container.querySelectorAll('.text-muted-foreground');
|
||||
expect(subtitles.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,10 +21,10 @@ describe('SwitchButtonGroup 组件', () => {
|
||||
const buttonA = screen.getByRole('button', { name: /选项A/i });
|
||||
const buttonB = screen.getByRole('button', { name: /选项B/i });
|
||||
|
||||
// 选中的按钮有 bg-white text-blue-600 shadow-sm 类
|
||||
expect(buttonA).toHaveClass('bg-white', 'text-blue-600', 'shadow-sm');
|
||||
// 未选中的按钮有 text-gray-500 类
|
||||
expect(buttonB).toHaveClass('text-gray-500');
|
||||
// 选中的按钮有 bg-background text-primary shadow-sm 类
|
||||
expect(buttonA).toHaveClass('bg-background', 'text-primary', 'shadow-sm');
|
||||
// 未选中的按钮有 text-muted-foreground 类
|
||||
expect(buttonB).toHaveClass('text-muted-foreground');
|
||||
});
|
||||
|
||||
it('点击未选中按钮时应触发 onChange 并传入选中值', () => {
|
||||
@@ -122,8 +122,8 @@ describe('SwitchButtonGroup 组件', () => {
|
||||
const button2 = screen.getByRole('button', { name: /^2$/i });
|
||||
const button4 = screen.getByRole('button', { name: /^4$/i });
|
||||
|
||||
expect(button2).toHaveClass('text-gray-500');
|
||||
expect(button4).toHaveClass('bg-white', 'text-blue-600', 'shadow-sm');
|
||||
expect(button2).toHaveClass('text-muted-foreground');
|
||||
expect(button4).toHaveClass('bg-background', 'text-primary', 'shadow-sm');
|
||||
});
|
||||
|
||||
it('点击 number 选项时应传回 number 值', () => {
|
||||
|
||||
@@ -334,7 +334,7 @@ describe('TextInputArea 组件', () => {
|
||||
);
|
||||
|
||||
const btn = screen.getByText('主要');
|
||||
expect(btn).toHaveClass('bg-blue-600', 'text-white');
|
||||
expect(btn).toHaveClass('bg-primary', 'text-white');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -346,7 +346,7 @@ describe('TextInputArea 组件', () => {
|
||||
|
||||
it('不设置 title 时不渲染标题', () => {
|
||||
const { container } = render(<TextInputArea value="" onChange={() => {}} />);
|
||||
expect(container.querySelector('.text-gray-500')).not.toBeInTheDocument();
|
||||
expect(container.querySelector('.text-muted-foreground')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user