fix: 修复暗黑模式样式兼容性
- 在 tailwind.config.js 中启用 darkMode: 'class' 并扩展语义化颜色变量 - 将全项目硬编码颜色(bg-white、text-gray-*、border-gray-* 等)替换为语义化类名 - 修复 popup/index.html 硬编码浅色背景色 - 同步更新相关单元测试中的类名断言
This commit is contained in:
@@ -179,10 +179,10 @@ export default function FileMode() {
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className={`flex flex-col items-center justify-center min-h-[180px] border-2 border-dashed rounded-xl p-8 cursor-pointer transition-all duration-200 ${
|
||||
isDragging
|
||||
? 'border-blue-500 bg-blue-50'
|
||||
? 'border-primary bg-primary/10'
|
||||
: info
|
||||
? 'border-blue-500 bg-blue-50/50'
|
||||
: 'border-gray-300 bg-gray-50 hover:border-blue-500 hover:bg-blue-50/50'
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-input bg-muted hover:border-primary hover:bg-primary/5'
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
@@ -195,23 +195,23 @@ export default function FileMode() {
|
||||
}}
|
||||
/>
|
||||
{isLoading ? (
|
||||
<div className="w-10 h-10 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin" />
|
||||
<div className="w-10 h-10 border-4 border-primary/30 border-t-blue-600 rounded-full animate-spin" />
|
||||
) : info ? (
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<Upload className="w-10 h-10 text-blue-600" />
|
||||
<Upload className="w-10 h-10 text-primary" />
|
||||
<span className="text-sm font-bold">{info.name}</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{formatFileSize(info.size)} · {info.type}
|
||||
</span>
|
||||
<span className="text-xs text-gray-400">{t('clickOrDropToReplace')}</span>
|
||||
<span className="text-xs text-muted-foreground">{t('clickOrDropToReplace')}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<Upload className="w-10 h-10 text-gray-400" />
|
||||
<span className="text-sm text-gray-500 font-semibold">
|
||||
<Upload className="w-10 h-10 text-muted-foreground" />
|
||||
<span className="text-sm text-muted-foreground font-semibold">
|
||||
{t('clickOrDropToFile')}
|
||||
</span>
|
||||
<span className="text-xs text-gray-400">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('maxFileSize', { max: `${MAX_FILE_SIZE / 1024 / 1024} MB` })}
|
||||
</span>
|
||||
</div>
|
||||
@@ -228,9 +228,9 @@ export default function FileMode() {
|
||||
)}
|
||||
|
||||
{result && (
|
||||
<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">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-xs font-bold text-gray-500">{t('base64Output')}</span>
|
||||
<span className="text-xs font-bold text-muted-foreground">{t('base64Output')}</span>
|
||||
<div className="flex gap-1">
|
||||
<CopyButton text={result.rawBase64} tooltip={t('copyRaw')} />
|
||||
<CopyButton text={result.output} tooltip={t('copyDataUri')} color="info" />
|
||||
@@ -247,17 +247,17 @@ export default function FileMode() {
|
||||
showCount
|
||||
/>
|
||||
<div className="flex items-center gap-4 mt-2">
|
||||
<span className="text-xs text-gray-400">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('originalSize')}: {formatFileSize(result.originalBytes)}
|
||||
</span>
|
||||
<span className="text-xs text-gray-400">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('encodedSize')}: {formatFileSize(result.outputBytes)}
|
||||
</span>
|
||||
<div className="flex-1" />
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClear}
|
||||
className="flex items-center gap-1 px-2 py-1 text-xs text-gray-500 hover:bg-gray-100 rounded-md transition-colors"
|
||||
className="flex items-center gap-1 px-2 py-1 text-xs text-muted-foreground hover:bg-accent rounded-md transition-colors"
|
||||
>
|
||||
<Trash2 className="w-3 h-3" />
|
||||
{t('clear')}
|
||||
@@ -270,7 +270,7 @@ export default function FileMode() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClear}
|
||||
className="flex items-center gap-1 px-3 py-2 text-sm text-gray-500 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground hover:bg-accent rounded-lg transition-colors"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
{t('clear')}
|
||||
|
||||
@@ -185,10 +185,10 @@ export default function ImageMode() {
|
||||
onClick={() => imageInputRef.current?.click()}
|
||||
className={`flex flex-col items-center justify-center min-h-[180px] border-2 border-dashed rounded-xl p-8 cursor-pointer transition-all duration-200 ${
|
||||
isDragging
|
||||
? 'border-blue-500 bg-blue-50'
|
||||
? 'border-primary bg-primary/10'
|
||||
: info
|
||||
? 'border-blue-500 bg-blue-50/50'
|
||||
: 'border-gray-300 bg-gray-50 hover:border-blue-500 hover:bg-blue-50/50'
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-input bg-muted hover:border-primary hover:bg-primary/5'
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
@@ -202,7 +202,7 @@ export default function ImageMode() {
|
||||
}}
|
||||
/>
|
||||
{isLoading ? (
|
||||
<div className="w-10 h-10 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin" />
|
||||
<div className="w-10 h-10 border-4 border-primary/30 border-t-blue-600 rounded-full animate-spin" />
|
||||
) : info ? (
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
{result && (
|
||||
@@ -213,18 +213,18 @@ export default function ImageMode() {
|
||||
/>
|
||||
)}
|
||||
<span className="text-sm font-bold">{info.name}</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{formatFileSize(info.size)} · {info.type}
|
||||
</span>
|
||||
<span className="text-xs text-gray-400">{t('clickOrDropToReplace')}</span>
|
||||
<span className="text-xs text-muted-foreground">{t('clickOrDropToReplace')}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<Image className="w-10 h-10 text-gray-400" />
|
||||
<span className="text-sm text-gray-500 font-semibold">
|
||||
<Image className="w-10 h-10 text-muted-foreground" />
|
||||
<span className="text-sm text-muted-foreground font-semibold">
|
||||
{t('clickOrDropToImage')}
|
||||
</span>
|
||||
<span className="text-xs text-gray-400">{t('supportedFormats')}</span>
|
||||
<span className="text-xs text-muted-foreground">{t('supportedFormats')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -239,24 +239,24 @@ export default function ImageMode() {
|
||||
)}
|
||||
|
||||
{result && (
|
||||
<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">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-xs font-bold text-gray-500">{t('base64Output')}</span>
|
||||
<span className="text-xs font-bold text-muted-foreground">{t('base64Output')}</span>
|
||||
<div className="flex gap-1">
|
||||
<CopyButton text={result.rawBase64} tooltip={t('copyRaw')} />
|
||||
<CopyButton text={result.output} tooltip={t('copyDataUri')} color="info" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="font-mono text-xs break-all max-h-[200px] overflow-y-auto leading-relaxed text-blue-600 font-semibold">
|
||||
<div className="font-mono text-xs break-all max-h-[200px] overflow-y-auto leading-relaxed text-primary font-semibold">
|
||||
{result.output.length > 2000
|
||||
? `${result.output.substring(0, 2000)}...`
|
||||
: result.output}
|
||||
</div>
|
||||
<div className="flex gap-4 mt-2">
|
||||
<span className="text-xs text-gray-400">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('originalSize')}: {formatFileSize(result.originalBytes)}
|
||||
</span>
|
||||
<span className="text-xs text-gray-400">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('encodedSize')}: {formatFileSize(result.outputBytes)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -267,7 +267,7 @@ export default function ImageMode() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClear}
|
||||
className="flex items-center gap-1 px-3 py-2 text-sm text-gray-500 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
className="flex items-center gap-1 px-3 py-2 text-sm text-muted-foreground hover:bg-accent rounded-lg transition-colors"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
{t('clear')}
|
||||
|
||||
@@ -120,12 +120,12 @@ export default function TextMode({ onSwitchToImageMode }: TextModeProps = {}) {
|
||||
/>
|
||||
|
||||
{showImageHint && (
|
||||
<div className="flex items-center justify-between p-3 rounded-lg bg-blue-50 border border-blue-200">
|
||||
<span className="text-sm text-blue-700">{t('imageDataUriHint')}</span>
|
||||
<div className="flex items-center justify-between p-3 rounded-lg bg-primary/10 border border-primary/30">
|
||||
<span className="text-sm text-primary">{t('imageDataUriHint')}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onSwitchToImageMode}
|
||||
className="px-3 py-1 text-sm font-medium text-blue-600 hover:bg-blue-100 rounded-md transition-colors"
|
||||
className="px-3 py-1 text-sm font-medium text-primary hover:bg-blue-100 rounded-md transition-colors"
|
||||
>
|
||||
{t('switchToImageMode')}
|
||||
</button>
|
||||
@@ -133,9 +133,9 @@ export default function TextMode({ onSwitchToImageMode }: TextModeProps = {}) {
|
||||
)}
|
||||
|
||||
{output && (
|
||||
<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">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-xs font-bold text-gray-500">{outputLabel}</span>
|
||||
<span className="text-xs font-bold text-muted-foreground">{outputLabel}</span>
|
||||
<CopyButton text={output} />
|
||||
</div>
|
||||
<TextInputArea
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function ToolCard({
|
||||
|
||||
return (
|
||||
<div
|
||||
className="group relative rounded-2xl border border-gray-200 h-full box-border transition-all duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] hover:-translate-y-1"
|
||||
className="group relative rounded-2xl border border-border h-full box-border transition-all duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] hover:-translate-y-1"
|
||||
style={{
|
||||
borderColor: undefined,
|
||||
['--hover-color' as string]: colorCode,
|
||||
@@ -65,18 +65,18 @@ export default function ToolCard({
|
||||
<IconComponent size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-bold text-sm leading-tight text-gray-900 flex items-center gap-1">
|
||||
<span className="font-bold text-sm leading-tight text-foreground flex items-center gap-1">
|
||||
{title}
|
||||
</span>
|
||||
{description && (
|
||||
<span className="block text-xs text-gray-500 font-medium mt-1 overflow-hidden text-ellipsis whitespace-nowrap max-w-[200px]">
|
||||
<span className="block text-xs text-muted-foreground font-medium mt-1 overflow-hidden text-ellipsis whitespace-nowrap max-w-[200px]">
|
||||
{description}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight
|
||||
className="w-3 h-3 text-gray-300 mt-1 transition-all duration-300 ease-in-out group-hover:translate-x-1"
|
||||
className="w-3 h-3 text-muted-foreground mt-1 transition-all duration-300 ease-in-out group-hover:translate-x-1"
|
||||
style={{ color: undefined }}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.color = colorCode;
|
||||
@@ -88,9 +88,7 @@ export default function ToolCard({
|
||||
</div>
|
||||
|
||||
{snapshot != null && (
|
||||
<div className="mt-auto pt-4 border-t border-dashed border-gray-200 w-full">
|
||||
{snapshot}
|
||||
</div>
|
||||
<div className="mt-auto pt-4 border-t border-dashed border-border w-full">{snapshot}</div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -65,14 +65,14 @@ export default function HtmlToMarkdownPage() {
|
||||
<button
|
||||
onClick={handleDownload}
|
||||
disabled={!result.markdown}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-foreground bg-background border border-input rounded-lg hover:bg-muted disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
<Download size={14} />
|
||||
{t('download')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleClear}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-foreground bg-background border border-input rounded-lg hover:bg-muted transition-colors"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
{t('clear')}
|
||||
@@ -95,10 +95,10 @@ export default function HtmlToMarkdownPage() {
|
||||
>
|
||||
{/* HTML 输入区 */}
|
||||
{showInput && (
|
||||
<div className="border border-gray-200 rounded-xl overflow-hidden flex flex-col">
|
||||
<div className="flex items-center justify-between px-4 py-2 bg-blue-50/50 border-b border-gray-200">
|
||||
<span className="text-xs font-bold text-gray-500">{t('inputLabel')}</span>
|
||||
<span className="text-xs text-gray-400">
|
||||
<div className="border border-border rounded-xl overflow-hidden flex flex-col">
|
||||
<div className="flex items-center justify-between px-4 py-2 bg-primary/5 border-b border-border">
|
||||
<span className="text-xs font-bold text-muted-foreground">{t('inputLabel')}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('charCount', { count: html.length })}
|
||||
</span>
|
||||
</div>
|
||||
@@ -113,15 +113,15 @@ export default function HtmlToMarkdownPage() {
|
||||
|
||||
{/* Markdown 输出区 */}
|
||||
{showOutput && (
|
||||
<div className="border border-gray-200 rounded-xl overflow-hidden flex flex-col">
|
||||
<div className="flex items-center justify-between px-4 py-2 bg-blue-50/50 border-b border-gray-200">
|
||||
<span className="text-xs font-bold text-gray-500">
|
||||
<div className="border border-border rounded-xl overflow-hidden flex flex-col">
|
||||
<div className="flex items-center justify-between px-4 py-2 bg-primary/5 border-b border-border">
|
||||
<span className="text-xs font-bold text-muted-foreground">
|
||||
{(previewMode as string) === 'markdown'
|
||||
? t('markdownOutputLabel')
|
||||
: t('previewLabel')}
|
||||
</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-xs text-gray-400">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('charCount', { count: result.markdownLength })}
|
||||
</span>
|
||||
<CopyButton text={result.markdown} size="small" />
|
||||
@@ -132,11 +132,13 @@ export default function HtmlToMarkdownPage() {
|
||||
<textarea
|
||||
value={result.markdown}
|
||||
readOnly
|
||||
className="flex-1 min-h-[400px] p-4 font-mono text-[0.85rem] leading-relaxed resize-none focus:outline-none bg-gray-50"
|
||||
className="flex-1 min-h-[400px] p-4 font-mono text-[0.85rem] leading-relaxed resize-none focus:outline-none bg-muted"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex-1 p-4 min-h-[400px] overflow-auto bg-white font-mono text-[0.85rem] leading-relaxed whitespace-pre-wrap break-words">
|
||||
{result.markdown || <span className="text-gray-400">{t('emptyHint')}</span>}
|
||||
<div className="flex-1 p-4 min-h-[400px] overflow-auto bg-background font-mono text-[0.85rem] leading-relaxed whitespace-pre-wrap break-words">
|
||||
{result.markdown || (
|
||||
<span className="text-muted-foreground">{t('emptyHint')}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -14,14 +14,14 @@ export default function DiffNavigator({ total, currentIndex, onPrev, onNext }: D
|
||||
|
||||
if (total === 0) {
|
||||
return (
|
||||
<div className="flex items-center justify-center gap-3 p-2.5 rounded-lg bg-blue-50 border border-blue-200">
|
||||
<span className="text-sm font-bold text-gray-500">{t('jsonDiff:noDiffs')}</span>
|
||||
<div className="flex items-center justify-center gap-3 p-2.5 rounded-lg bg-primary/10 border border-primary/30">
|
||||
<span className="text-sm font-bold text-muted-foreground">{t('jsonDiff:noDiffs')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center gap-3 p-2.5 rounded-lg bg-blue-50 border border-blue-200">
|
||||
<div className="flex items-center justify-center gap-3 p-2.5 rounded-lg bg-primary/10 border border-primary/30">
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t('jsonDiff:previousDiff')}
|
||||
|
||||
@@ -27,14 +27,14 @@ export default function DiffResult({ result, viewMode, activePath }: DiffResultP
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-3 rounded-lg bg-white border border-gray-200 font-mono text-sm overflow-x-auto min-h-[200px] max-h-[480px] overflow-y-auto">
|
||||
<div className="p-3 rounded-lg bg-background border border-border font-mono text-sm overflow-x-auto min-h-[200px] max-h-[480px] overflow-y-auto">
|
||||
<UnifiedView node={result.root} depth={0} activePath={activePath} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const SectionLabel = ({ text }: { text: string }) => (
|
||||
<span className="block mb-1.5 text-[11px] font-extrabold tracking-wider text-gray-500 uppercase">
|
||||
<span className="block mb-1.5 text-[11px] font-extrabold tracking-wider text-muted-foreground uppercase">
|
||||
{text}
|
||||
</span>
|
||||
);
|
||||
@@ -61,7 +61,7 @@ const colorForType = (type: DiffType): string => {
|
||||
if (type === 'added') return 'text-green-600';
|
||||
if (type === 'removed') return 'text-red-600';
|
||||
if (type === 'modified') return 'text-amber-600';
|
||||
return 'text-gray-900';
|
||||
return 'text-foreground';
|
||||
};
|
||||
|
||||
const bgForType = (type: DiffType): string | undefined => {
|
||||
|
||||
@@ -120,17 +120,17 @@ export default function JsonConvertSection({
|
||||
|
||||
{/* 转换结果 */}
|
||||
{result && result.output ? (
|
||||
<div className="relative rounded-lg bg-white border border-gray-200 overflow-hidden">
|
||||
<div className="relative rounded-lg bg-background border border-border overflow-hidden">
|
||||
{/* 结果头部 */}
|
||||
<div className="flex justify-between items-center px-4 py-2 border-b border-gray-200 bg-gray-50">
|
||||
<div className="flex justify-between items-center px-4 py-2 border-b border-border bg-muted">
|
||||
<div className="flex gap-4 items-center">
|
||||
<span className="text-[11px] font-extrabold text-gray-500">
|
||||
<span className="text-[11px] font-extrabold text-muted-foreground">
|
||||
{t(`jsonFormat:${pk}OutputLabel`)}
|
||||
</span>
|
||||
<span className="text-[10px] text-gray-400">
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{t('jsonFormat:originalSize')}: {formatByteSize(result.originalBytes)}
|
||||
</span>
|
||||
<span className="text-[10px] text-gray-400">
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{t('jsonFormat:formattedSize')}: {formatByteSize(result.outputBytes)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -143,8 +143,10 @@ export default function JsonConvertSection({
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-4 rounded-lg bg-gray-50 border border-dashed border-gray-300 text-center">
|
||||
<p className="text-sm font-semibold text-gray-500">{t(`jsonFormat:${pk}EmptyHint`)}</p>
|
||||
<div className="p-4 rounded-lg bg-muted border border-dashed border-input text-center">
|
||||
<p className="text-sm font-semibold text-muted-foreground">
|
||||
{t(`jsonFormat:${pk}EmptyHint`)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@ export default function JsonDiffInput({
|
||||
}: JsonDiffInputProps) {
|
||||
return (
|
||||
<div className="flex-1 min-w-0">
|
||||
<span className="block mb-1.5 text-[11px] font-extrabold tracking-wider text-gray-500 uppercase">
|
||||
<span className="block mb-1.5 text-[11px] font-extrabold tracking-wider text-muted-foreground uppercase">
|
||||
{label}
|
||||
</span>
|
||||
<TextInputArea
|
||||
|
||||
@@ -73,7 +73,7 @@ export default function JsonFormatSection() {
|
||||
<div className="flex flex-col sm:flex-row gap-3 justify-between items-stretch sm:items-center">
|
||||
<div className="flex gap-3 items-center">
|
||||
{/* 缩进选择 */}
|
||||
<span className="text-[11px] font-extrabold text-gray-500">
|
||||
<span className="text-[11px] font-extrabold text-muted-foreground">
|
||||
{t('jsonFormat:indentSize')}
|
||||
</span>
|
||||
<SwitchButtonGroup
|
||||
@@ -90,7 +90,7 @@ export default function JsonFormatSection() {
|
||||
type="checkbox"
|
||||
checked={sortKeys}
|
||||
onChange={(e) => setSortKeys(e.target.checked)}
|
||||
className="h-4 w-4 rounded border-gray-300 text-blue-500 focus:ring-blue-500"
|
||||
className="h-4 w-4 rounded border-input text-blue-500 focus:ring-primary"
|
||||
/>
|
||||
<span className="text-xs font-bold">{t('jsonFormat:sortKeys')}</span>
|
||||
</label>
|
||||
@@ -119,8 +119,8 @@ export default function JsonFormatSection() {
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
rows={6}
|
||||
className={`w-full rounded-lg border ${
|
||||
error ? 'border-red-300' : 'border-gray-200'
|
||||
} p-3 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 resize-y`}
|
||||
error ? 'border-red-300' : 'border-border'
|
||||
} p-3 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-primary resize-y`}
|
||||
/>
|
||||
{error && (
|
||||
<p className="mx-3 mt-1 text-xs font-semibold text-red-500">
|
||||
@@ -131,17 +131,17 @@ export default function JsonFormatSection() {
|
||||
|
||||
{/* 格式化结果 */}
|
||||
{result && result.formatted ? (
|
||||
<div className="relative rounded-lg bg-white border border-gray-200 overflow-hidden">
|
||||
<div className="relative rounded-lg bg-background border border-border overflow-hidden">
|
||||
{/* 结果头部 */}
|
||||
<div className="flex justify-between items-center px-4 py-2 border-b border-gray-200 bg-gray-50">
|
||||
<div className="flex justify-between items-center px-4 py-2 border-b border-border bg-muted">
|
||||
<div className="flex gap-4 items-center">
|
||||
<span className="text-[11px] font-extrabold text-gray-500">
|
||||
<span className="text-[11px] font-extrabold text-muted-foreground">
|
||||
{t('jsonFormat:outputLabel')}
|
||||
</span>
|
||||
<span className="text-[10px] text-gray-400">
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{t('jsonFormat:originalSize')}: {formatByteSize(result.originalBytes)}
|
||||
</span>
|
||||
<span className="text-[10px] text-gray-400">
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{t('jsonFormat:formattedSize')}: {formatByteSize(result.formattedBytes)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -154,8 +154,8 @@ export default function JsonFormatSection() {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-4 rounded-lg bg-gray-50 border border-dashed border-gray-300 text-center">
|
||||
<p className="text-sm font-semibold text-gray-500">{t('jsonFormat:emptyHint')}</p>
|
||||
<div className="p-4 rounded-lg bg-muted border border-dashed border-input text-center">
|
||||
<p className="text-sm font-semibold text-muted-foreground">{t('jsonFormat:emptyHint')}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -121,17 +121,17 @@ const NodeRow = ({
|
||||
onClick={() => setOverride(expanded ? 'closed' : 'open')}
|
||||
className={`cursor-pointer pr-1 py-0.5 ${bg ?? ''} ${
|
||||
isActive ? 'ring-2 ring-blue-500 rounded' : ''
|
||||
} flex items-center gap-1 whitespace-nowrap hover:${bg ? 'bg-opacity-80' : 'bg-gray-50'}`}
|
||||
} flex items-center gap-1 whitespace-nowrap hover:${bg ? 'bg-opacity-80' : 'bg-muted'}`}
|
||||
style={{ paddingLeft: `${depth * 1.5}rem` }}
|
||||
>
|
||||
<span className="w-3 text-gray-500 text-[11px]">{expanded ? '▾' : '▸'}</span>
|
||||
<span className="w-3 text-muted-foreground text-[11px]">{expanded ? '▾' : '▸'}</span>
|
||||
{!isRoot && (
|
||||
<span className="text-gray-900 font-bold">{isArrayKeyDisplay(node.key)}:</span>
|
||||
<span className="text-foreground font-bold">{isArrayKeyDisplay(node.key)}:</span>
|
||||
)}
|
||||
<span className="text-gray-500">{open}</span>
|
||||
{!expanded && <span className="text-gray-400 italic">{summarize(value)}</span>}
|
||||
<span className="text-muted-foreground">{open}</span>
|
||||
{!expanded && <span className="text-muted-foreground italic">{summarize(value)}</span>}
|
||||
{!expanded && (
|
||||
<span className="text-gray-500">
|
||||
<span className="text-muted-foreground">
|
||||
{close}
|
||||
{isLastChild ? '' : ','}
|
||||
</span>
|
||||
@@ -154,7 +154,7 @@ const NodeRow = ({
|
||||
)}
|
||||
{expanded && (
|
||||
<div
|
||||
className="text-gray-500 whitespace-nowrap"
|
||||
className="text-muted-foreground whitespace-nowrap"
|
||||
style={{ paddingLeft: `${depth * 1.5 + 1.0625}rem` }}
|
||||
>
|
||||
{close}
|
||||
@@ -175,8 +175,8 @@ const NodeRow = ({
|
||||
style={{ paddingLeft: `${depth * 1.5}rem` }}
|
||||
>
|
||||
<span className="w-3" />
|
||||
{!isRoot && <span className="text-gray-900 font-bold">{isArrayKeyDisplay(node.key)}:</span>}
|
||||
<span className={valueColor ?? 'text-gray-900'}>
|
||||
{!isRoot && <span className="text-foreground font-bold">{isArrayKeyDisplay(node.key)}:</span>}
|
||||
<span className={valueColor ?? 'text-foreground'}>
|
||||
{formatPrimitive(value)}
|
||||
{isLastChild ? '' : ','}
|
||||
</span>
|
||||
@@ -206,7 +206,7 @@ export default function JsonTree({
|
||||
}: JsonTreeProps) {
|
||||
const sideKey = useMemo(() => side, [side]);
|
||||
return (
|
||||
<div className="p-3 rounded-lg bg-white border border-gray-200 font-mono text-sm overflow-x-auto min-h-[200px] max-h-[480px] overflow-y-auto">
|
||||
<div className="p-3 rounded-lg bg-background border border-border font-mono text-sm overflow-x-auto min-h-[200px] max-h-[480px] overflow-y-auto">
|
||||
<NodeRow
|
||||
node={node}
|
||||
side={sideKey}
|
||||
|
||||
@@ -219,8 +219,10 @@ export default function Index() {
|
||||
<DiffResult result={diffResult} viewMode={viewMode} activePath={activePath} />
|
||||
</>
|
||||
) : (
|
||||
<div className="p-4 rounded-lg bg-gray-50 border border-dashed border-gray-300 text-center">
|
||||
<p className="text-sm font-semibold text-gray-500">{t('jsonDiff:emptyHint')}</p>
|
||||
<div className="p-4 rounded-lg bg-muted border border-dashed border-input text-center">
|
||||
<p className="text-sm font-semibold text-muted-foreground">
|
||||
{t('jsonDiff:emptyHint')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
+4
-4
@@ -32,7 +32,7 @@ const Section = ({ title, content, color }: SectionProps) => {
|
||||
<CopyButton text={JSON.stringify(content)} size="small" />
|
||||
</div>
|
||||
</div>
|
||||
<pre className="m-0 p-3 bg-white/60 rounded-lg text-xs font-mono overflow-x-auto whitespace-pre-wrap break-all border border-black/5">
|
||||
<pre className="m-0 p-3 bg-background/60 rounded-lg text-xs font-mono overflow-x-auto whitespace-pre-wrap break-all border border-black/5">
|
||||
{content ? stringifyJson(content) : t('jwt:invalidFormat')}
|
||||
</pre>
|
||||
</div>
|
||||
@@ -91,14 +91,14 @@ export default function Index() {
|
||||
content={result.payload}
|
||||
color="#d63aff" // JWT.io Payload Color
|
||||
/>
|
||||
<div className="p-4 rounded-xl border border-blue-200 bg-blue-50">
|
||||
<div className="p-4 rounded-xl border border-primary/30 bg-primary/10">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-sm font-bold tracking-wide text-blue-600">
|
||||
<span className="text-sm font-bold tracking-wide text-primary">
|
||||
{t('jwt:signatureTitle')}
|
||||
</span>
|
||||
<CopyButton text={JSON.stringify(result.raw.signature)} size="small" />
|
||||
</div>
|
||||
<span className="block text-sm font-mono break-all text-gray-500 bg-white/60 p-3 rounded-lg border border-black/5">
|
||||
<span className="block text-sm font-mono break-all text-muted-foreground bg-background/60 p-3 rounded-lg border border-black/5">
|
||||
{result.signature || t('jwt:noSignature')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -168,21 +168,21 @@ export default function MarkdownToHtmlPage() {
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={handleClear}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-foreground bg-background border border-input rounded-lg hover:bg-muted transition-colors"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
{t('clear')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handlePrint}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-foreground bg-background border border-input rounded-lg hover:bg-muted transition-colors"
|
||||
>
|
||||
<Printer size={14} />
|
||||
{t('print')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDownload}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-foreground bg-background border border-input rounded-lg hover:bg-muted transition-colors"
|
||||
>
|
||||
<Download size={14} />
|
||||
{t('download')}
|
||||
@@ -205,10 +205,10 @@ export default function MarkdownToHtmlPage() {
|
||||
>
|
||||
{/* Markdown 输入区 */}
|
||||
{showInput && (
|
||||
<div className="border border-gray-200 rounded-xl overflow-hidden flex flex-col">
|
||||
<div className="flex items-center justify-between px-4 py-2 bg-blue-50/50 border-b border-gray-200">
|
||||
<span className="text-xs font-bold text-gray-500">{t('inputLabel')}</span>
|
||||
<span className="text-xs text-gray-400">
|
||||
<div className="border border-border rounded-xl overflow-hidden flex flex-col">
|
||||
<div className="flex items-center justify-between px-4 py-2 bg-primary/5 border-b border-border">
|
||||
<span className="text-xs font-bold text-muted-foreground">{t('inputLabel')}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('charCount', { count: markdown.length })}
|
||||
</span>
|
||||
</div>
|
||||
@@ -223,13 +223,13 @@ export default function MarkdownToHtmlPage() {
|
||||
|
||||
{/* 预览/输出区 */}
|
||||
{showPreview && (
|
||||
<div className="border border-gray-200 rounded-xl overflow-hidden flex flex-col">
|
||||
<div className="flex items-center justify-between px-4 py-2 bg-blue-50/50 border-b border-gray-200">
|
||||
<span className="text-xs font-bold text-gray-500">
|
||||
<div className="border border-border rounded-xl overflow-hidden flex flex-col">
|
||||
<div className="flex items-center justify-between px-4 py-2 bg-primary/5 border-b border-border">
|
||||
<span className="text-xs font-bold text-muted-foreground">
|
||||
{(previewMode as string) === 'html' ? t('htmlOutputLabel') : t('previewLabel')}
|
||||
</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-xs text-gray-400">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('charCount', { count: result.htmlLength })}
|
||||
</span>
|
||||
<CopyButton text={result.html} size="small" />
|
||||
@@ -240,10 +240,10 @@ export default function MarkdownToHtmlPage() {
|
||||
<textarea
|
||||
value={result.html}
|
||||
readOnly
|
||||
className="flex-1 min-h-[400px] p-4 font-mono text-[0.8rem] leading-relaxed resize-none focus:outline-none bg-gray-50"
|
||||
className="flex-1 min-h-[400px] p-4 font-mono text-[0.8rem] leading-relaxed resize-none focus:outline-none bg-muted"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex-1 p-4 min-h-[400px] overflow-auto bg-white">
|
||||
<div className="flex-1 p-4 min-h-[400px] overflow-auto bg-background">
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
title="markdown-preview"
|
||||
|
||||
@@ -8,8 +8,8 @@ interface AutoRefreshToggleProps {
|
||||
export default function AutoRefreshToggle({ autoRefresh, onChange }: AutoRefreshToggleProps) {
|
||||
const { t } = useLazyTranslation('storageCleaner');
|
||||
return (
|
||||
<div className="mb-3 p-4 rounded-lg bg-white border border-gray-200 flex justify-between items-center shadow-sm transition-all hover:shadow-md">
|
||||
<span className="text-sm font-bold text-gray-700 px-3">
|
||||
<div className="mb-3 p-4 rounded-lg bg-background border border-border flex justify-between items-center shadow-sm transition-all hover:shadow-md">
|
||||
<span className="text-sm font-bold text-foreground px-3">
|
||||
{t('storageCleaner:autoRefresh')}
|
||||
</span>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
@@ -19,7 +19,7 @@ export default function AutoRefreshToggle({ autoRefresh, onChange }: AutoRefresh
|
||||
onChange={(e) => onChange(e.target.checked)}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-amber-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-amber-500" />
|
||||
<div className="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-amber-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-background after:border-input after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-amber-500" />
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function CleaningResult({ result }: CleaningResultProps) {
|
||||
)}
|
||||
<span
|
||||
className={`text-sm font-semibold leading-relaxed ${
|
||||
isSuccess ? 'text-green-700' : 'text-red-700'
|
||||
isSuccess ? 'text-green-600' : 'text-red-700'
|
||||
}`}
|
||||
>
|
||||
{isSuccess
|
||||
|
||||
@@ -13,7 +13,7 @@ export default function ErrorDisplay({ error }: ErrorDisplayProps) {
|
||||
<div className="flex flex-col items-center justify-center rounded-lg p-6 shadow-lg border border-red-200 bg-red-50">
|
||||
<AlertCircle className="h-9 w-9 text-red-500 mb-3" />
|
||||
<p className="text-sm font-bold leading-relaxed mb-4 text-red-600">{error}</p>
|
||||
<p className="text-xs font-medium leading-relaxed text-gray-500">
|
||||
<p className="text-xs font-medium leading-relaxed text-muted-foreground">
|
||||
{t('storageCleaner:errorStandardOnly')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -22,23 +22,23 @@ export default function OptionItem({
|
||||
className={`flex justify-between items-center py-2 px-3 rounded-lg transition-all duration-200 ${
|
||||
checked
|
||||
? 'bg-amber-50 border border-amber-200'
|
||||
: 'bg-transparent border border-transparent hover:bg-gray-50 hover:shadow-sm'
|
||||
: 'bg-transparent border border-transparent hover:bg-muted hover:shadow-sm'
|
||||
}`}
|
||||
>
|
||||
<div className="flex-1 min-w-0 mr-3">
|
||||
<span
|
||||
className={`block text-xs leading-tight truncate transition-colors ${
|
||||
checked ? 'text-amber-700 font-bold' : 'text-gray-900 font-bold'
|
||||
checked ? 'text-amber-700 font-bold' : 'text-foreground font-bold'
|
||||
}`}
|
||||
>
|
||||
{t(labelKey)}
|
||||
</span>
|
||||
{size !== undefined && size > 0 ? (
|
||||
<span className="block text-[10px] font-semibold text-gray-500 mt-0.5 opacity-80">
|
||||
<span className="block text-[10px] font-semibold text-muted-foreground mt-0.5 opacity-80">
|
||||
{isCount ? `${size} ${t('storageCleaner:countUnit')}` : formatSize(size)}
|
||||
</span>
|
||||
) : (
|
||||
<span className="block text-[10px] font-medium text-gray-400 mt-0.5 italic">
|
||||
<span className="block text-[10px] font-medium text-muted-foreground mt-0.5 italic">
|
||||
{t('storageCleaner:noData')}
|
||||
</span>
|
||||
)}
|
||||
@@ -47,7 +47,7 @@ export default function OptionItem({
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
className="h-4 w-4 rounded border-gray-300 text-amber-500 focus:ring-amber-500"
|
||||
className="h-4 w-4 rounded border-input text-amber-500 focus:ring-amber-500"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function StorageOptionsGrid({
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="mb-3 border rounded-lg bg-white shadow-sm overflow-hidden transition-all hover:shadow-md">
|
||||
<div className="mb-3 border rounded-lg bg-background shadow-sm overflow-hidden transition-all hover:shadow-md">
|
||||
<div className="p-3">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{optionKeys.map(({ key, isCount }) => (
|
||||
@@ -48,13 +48,15 @@ export default function StorageOptionsGrid({
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t" />
|
||||
<div className="flex justify-between items-center px-5 py-2 transition-colors hover:bg-gray-50 rounded-b-lg">
|
||||
<span className="text-xs font-bold text-gray-500">{t('storageCleaner:selectAll')}</span>
|
||||
<div className="flex justify-between items-center px-5 py-2 transition-colors hover:bg-muted rounded-b-lg">
|
||||
<span className="text-xs font-bold text-muted-foreground">
|
||||
{t('storageCleaner:selectAll')}
|
||||
</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={allSelected}
|
||||
onChange={(e) => onSelectAll(e.target.checked)}
|
||||
className="h-4 w-4 rounded border-gray-300 text-amber-500 focus:ring-amber-500"
|
||||
className="h-4 w-4 rounded border-input text-amber-500 focus:ring-amber-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -66,7 +66,9 @@ export default function Index() {
|
||||
borderColor: `${textStatisticsPageStyles.primaryColor}1a`,
|
||||
}}
|
||||
>
|
||||
<span className="text-gray-500 whitespace-nowrap mb-0 md:mb-1">{item.label}</span>
|
||||
<span className="text-muted-foreground whitespace-nowrap mb-0 md:mb-1">
|
||||
{item.label}
|
||||
</span>
|
||||
<span
|
||||
className="break-all font-semibold"
|
||||
style={{ color: textStatisticsPageStyles.primaryColor }}
|
||||
|
||||
@@ -37,17 +37,17 @@ const LiveClock = React.memo(({ unit, onUseNow }: LiveClockProps) => {
|
||||
}, [now, showMessage, t]);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3 px-4 py-2 mb-4 bg-blue-50 rounded-lg border border-blue-100">
|
||||
<span className="text-blue-700 font-bold text-[0.65rem] uppercase tracking-wider whitespace-nowrap">
|
||||
<div className="flex items-center gap-3 px-4 py-2 mb-4 bg-primary/10 rounded-lg border border-primary/20">
|
||||
<span className="text-primary font-bold text-[0.65rem] uppercase tracking-wider whitespace-nowrap">
|
||||
{t('timestamp:currentTs')}
|
||||
</span>
|
||||
<span className="flex-1 font-mono font-bold text-blue-700 text-sm tracking-tight leading-tight overflow-hidden text-ellipsis">
|
||||
<span className="flex-1 font-mono font-bold text-primary text-sm tracking-tight leading-tight overflow-hidden text-ellipsis">
|
||||
{displayVal}
|
||||
</span>
|
||||
<button
|
||||
onClick={handleUseNow}
|
||||
title={t('timestamp:useNowTooltip')}
|
||||
className="p-1.5 rounded-md bg-white text-blue-700 shadow-sm hover:bg-blue-700 hover:text-white transition-colors"
|
||||
className="p-1.5 rounded-md bg-background text-primary shadow-sm hover:bg-blue-700 hover:text-white transition-colors"
|
||||
>
|
||||
<Clock className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
@@ -37,7 +37,7 @@ const ResultView = React.memo(
|
||||
if (!result) {
|
||||
if (!showEmptyPlaceholder) return null;
|
||||
return (
|
||||
<div className="flex-1 flex items-center justify-center text-gray-400 text-sm font-semibold py-12 text-center">
|
||||
<div className="flex-1 flex items-center justify-center text-muted-foreground text-sm font-semibold py-12 text-center">
|
||||
{t('timestamp:resultEmpty')}
|
||||
</div>
|
||||
);
|
||||
@@ -45,12 +45,12 @@ const ResultView = React.memo(
|
||||
|
||||
return (
|
||||
<div className="animate-in fade-in duration-300">
|
||||
<span className="block text-gray-500 mb-3 text-xs font-bold">
|
||||
<span className="block text-muted-foreground mb-3 text-xs font-bold">
|
||||
{t('timestamp:resultLabel')}
|
||||
</span>
|
||||
|
||||
<div className="bg-blue-50 p-5 rounded-xl relative mb-4 border border-blue-200 flex justify-between items-center">
|
||||
<span className="font-mono font-bold text-blue-700 break-all pr-4 text-xl tracking-tight leading-tight">
|
||||
<div className="bg-primary/10 p-5 rounded-xl relative mb-4 border border-primary/30 flex justify-between items-center">
|
||||
<span className="font-mono font-bold text-primary break-all pr-4 text-xl tracking-tight leading-tight">
|
||||
{result}
|
||||
</span>
|
||||
<CopyButton
|
||||
@@ -61,18 +61,18 @@ const ResultView = React.memo(
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-blue-50/50 p-4 rounded-xl border border-blue-100 flex flex-col gap-3">
|
||||
<div className="bg-primary/5 p-4 rounded-xl border border-primary/20 flex flex-col gap-3">
|
||||
{[
|
||||
{ label: t('timestamp:relativeTime'), value: extraInfo?.relative },
|
||||
{ label: t('timestamp:iso8601'), value: extraInfo?.iso },
|
||||
{ label: t('timestamp:utcTime'), value: extraInfo?.utc },
|
||||
].map((item) => (
|
||||
<div key={item.label} className="flex justify-between items-center">
|
||||
<span className="text-gray-400 font-bold text-xs pr-2 whitespace-nowrap">
|
||||
<span className="text-muted-foreground font-bold text-xs pr-2 whitespace-nowrap">
|
||||
{item.label}
|
||||
</span>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<span className="font-mono text-blue-700 font-semibold text-xs break-all text-right">
|
||||
<span className="font-mono text-primary font-semibold text-xs break-all text-right">
|
||||
{item.value}
|
||||
</span>
|
||||
{item.value && (
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function Index() {
|
||||
{/* 桌面端 md+ 左右分栏;移动端单栏堆叠 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-stretch">
|
||||
{/* 左栏:转换工作台 */}
|
||||
<div className="p-5 rounded-xl bg-white border border-gray-200 shadow-sm">
|
||||
<div className="p-5 rounded-xl bg-background border border-border shadow-sm">
|
||||
{/* 模式切换 */}
|
||||
<SwitchButtonGroup
|
||||
value={mode}
|
||||
@@ -63,8 +63,8 @@ export default function Index() {
|
||||
value={mode === 'ts2dt' ? tsInput : dtInput}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
className={`w-full px-4 py-2.5 rounded-lg border ${
|
||||
error ? 'border-red-500' : 'border-gray-300'
|
||||
} bg-white text-sm font-mono font-semibold focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all`}
|
||||
error ? 'border-red-500' : 'border-input'
|
||||
} bg-background text-sm font-mono font-semibold focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all`}
|
||||
/>
|
||||
{error && <p className="text-red-500 text-xs mt-1">{error}</p>}
|
||||
|
||||
@@ -84,7 +84,7 @@ export default function Index() {
|
||||
<select
|
||||
value={zone}
|
||||
onChange={(e) => setZone(e.target.value as typeof zone)}
|
||||
className="flex-1 min-w-0 px-4 py-2.5 rounded-lg border border-gray-300 bg-white text-sm font-mono font-semibold focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
||||
className="flex-1 min-w-0 px-4 py-2.5 rounded-lg border border-input bg-background text-sm font-mono font-semibold focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"
|
||||
>
|
||||
{ZONES.map((z) => (
|
||||
<option key={z} value={z} className="text-sm font-semibold">
|
||||
@@ -98,14 +98,14 @@ export default function Index() {
|
||||
{/* 立即转换 */}
|
||||
<button
|
||||
onClick={convert}
|
||||
className="block mx-auto mt-4 mb-1 max-w-[240px] w-full py-2.5 text-sm font-semibold rounded-lg bg-blue-600 text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-all"
|
||||
className="block mx-auto mt-4 mb-1 max-w-[240px] w-full py-2.5 text-sm font-semibold rounded-lg bg-primary text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 transition-all"
|
||||
>
|
||||
{t('timestamp:convertButton')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 右栏:结果展示卡片 */}
|
||||
<div className="p-5 rounded-xl bg-white border border-gray-200 shadow-sm h-full flex flex-col">
|
||||
<div className="p-5 rounded-xl bg-background border border-border shadow-sm h-full flex flex-col">
|
||||
<ResultView result={result} mode={mode} unit={unit} zone={zone} showEmptyPlaceholder />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user