diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 26a949e0..7888459b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,10 +42,10 @@ jobs: pnpm install --frozen-lockfile if [ "${{ steps.changes.outputs.code }}" == "true" ]; then - # 运行所有测试:单元测试 + Storybook 组件测试 + E2E 测试 - TASKS="typecheck:run build i18n:run test:run test:storybook e2e:ci e2e:ci:mock e2e:ci:real" + # 运行所有测试:单元测试 + Storybook 组件测试 + E2E 测试 + 主题检查 + TASKS="typecheck:run build i18n:run theme:run test:run test:storybook e2e:ci e2e:ci:mock e2e:ci:real" else - TASKS="typecheck:run build i18n:run test:run test:storybook" + TASKS="typecheck:run build i18n:run theme:run test:run test:storybook" fi # Run turbo and capture output, pipefail ensures we get turbo's exit code @@ -115,10 +115,10 @@ jobs: E2E_TEST_MNEMONIC: ${{ secrets.E2E_TEST_MNEMONIC }} run: | if [ "${{ steps.changes.outputs.code }}" == "true" ]; then - # 运行所有测试:单元测试 + Storybook 组件测试 + E2E 测试 - pnpm turbo run typecheck:run build i18n:run test:run test:storybook e2e:ci e2e:ci:mock e2e:ci:real + # 运行所有测试:单元测试 + Storybook 组件测试 + E2E 测试 + 主题检查 + pnpm turbo run typecheck:run build i18n:run theme:run test:run test:storybook e2e:ci e2e:ci:mock e2e:ci:real else - pnpm turbo run typecheck:run build i18n:run test:run test:storybook + pnpm turbo run typecheck:run build i18n:run theme:run test:run test:storybook fi checks-standard: diff --git "a/docs/white-book/00-\345\277\205\350\257\273/best-practices.md" "b/docs/white-book/00-\345\277\205\350\257\273/best-practices.md" index acc252f2..9a697ce4 100644 --- "a/docs/white-book/00-\345\277\205\350\257\273/best-practices.md" +++ "b/docs/white-book/00-\345\277\205\350\257\273/best-practices.md" @@ -12,6 +12,9 @@ - ❌ 安装新 UI 库 → ✅ shadcn/ui(已集成) - ❌ 新建 CSS → ✅ Tailwind CSS - ❌ text-secondary → ✅ text-muted-foreground 或 bg-secondary text-secondary-foreground(详见白皮书 02-设计篇/02-视觉设计/theme-colors) +- ❌ bg-primary text-white → ✅ bg-primary text-primary-foreground(暗色模式下 text-white 对比度不足) +- ❌ bg-muted 无文字色 → ✅ bg-muted text-muted-foreground(详见白皮书 02-设计篇/02-视觉设计/dark-mode) +- ❌ bg-gray-100 无 dark: 变体 → ✅ bg-gray-100 dark:bg-gray-800 或使用 bg-muted - ❌ getByText('硬编码中文') → ✅ getByText(t('i18n.key')) 或 getByTestId - ❌ password/Password(宽泛含义) → ✅ walletLock(钱包锁)/ twoStepSecret(安全密码)/ payPassword(支付密码)等具体命名 - 圆形元素必须使用 aspect-square 标记,与 w-*/h-*/size-* 不冲突,是规范要求 diff --git "a/docs/white-book/02-\350\256\276\350\256\241\347\257\207/02-\350\247\206\350\247\211\350\256\276\350\256\241/dark-mode.md" "b/docs/white-book/02-\350\256\276\350\256\241\347\257\207/02-\350\247\206\350\247\211\350\256\276\350\256\241/dark-mode.md" new file mode 100644 index 00000000..f872505e --- /dev/null +++ "b/docs/white-book/02-\350\256\276\350\256\241\347\257\207/02-\350\247\206\350\247\211\350\256\276\350\256\241/dark-mode.md" @@ -0,0 +1,162 @@ +# 暗色模式最佳实践 + +> 确保应用在浅色和暗色主题下都有良好的可读性和视觉体验。 + +--- + +## 核心原则 + +### 1. 使用语义化颜色 Token + +**始终优先使用 shadcn/ui 的语义化颜色变量**,它们会自动适配主题: + +```tsx +// ✅ 正确 - 使用语义化颜色 +
+

辅助文字

+ +
+ +// ❌ 错误 - 硬编码颜色 +
+

辅助文字

+
+``` + +### 2. 配色对必须配套使用 + +每个 shadcn/ui 颜色变量都有对应的 `-foreground` 变体: + +| 背景色 | 前景色 | 使用场景 | +|--------|--------|----------| +| `bg-primary` | `text-primary-foreground` | 主要按钮 | +| `bg-secondary` | `text-secondary-foreground` | 次要按钮 | +| `bg-destructive` | `text-destructive-foreground` | 危险按钮 | +| `bg-muted` | `text-muted-foreground` | 低调区域 | +| `bg-accent` | `text-accent-foreground` | 强调区域 | +| `bg-card` | `text-card-foreground` | 卡片容器 | + +**例外**:`text-muted-foreground` 可以单独用于次要文字,不需要配套 `bg-muted`。 + +### 3. 禁止 `bg-primary text-white` + +```tsx +// ❌ 错误 - text-white 在暗色模式下不会变色 + + +// ✅ 正确 - text-primary-foreground 会自动适配主题 + +``` + +**原因**:在暗色模式下,`--primary` 变亮而 `--primary-foreground` 变暗,保持对比度。`text-white` 始终是白色,在亮色的 `bg-primary` 上可能对比度不足。 + +### 4. 不要将背景色用作文字色 + +```tsx +// ❌ 错误 - secondary 是背景色 +

看不见的文字

+ +// ✅ 正确 +

次要文字

+// 或用于按钮 + +``` + +### 5. `bg-muted` 必须指定文字颜色 + +```tsx +// ❌ 错误 - 没有文字颜色,可能继承错误的颜色 +
+ 这段文字可能不可见 +
+ +// ✅ 正确 +
+ 清晰可见 +
+``` + +--- + +## 硬编码颜色规则 + +### 需要 dark: 变体的情况 + +使用硬编码灰色时,**必须**添加暗色变体: + +```tsx +// ✅ 正确 - 有 dark: 变体 +
...
+

...

+
...
+ +// ❌ 错误 - 缺少 dark: 变体 +
...
+``` + +### 灰度映射参考 + +| 浅色 | 暗色 | +|------|------| +| gray-50 | gray-900 | +| gray-100 | gray-800 | +| gray-200 | gray-700 | +| gray-300 | gray-600 | +| gray-400 | gray-500 | +| gray-500 | gray-400 | + +### 可接受的例外 + +以下情况**不需要** dark: 变体: + +1. **半透明颜色在渐变/彩色背景上**: + ```tsx + // ✅ OK - 在渐变背景上 +
+ 半透明装饰 +

白色文字

+
+ ``` + +2. **相机/扫描器界面**: + ```tsx + // ✅ OK - 相机背景 +
+
+ ``` + +3. **固定颜色背景上的白字**(如 `bg-green-500`、`bg-red-500`): + ```tsx + // ✅ OK - 固定颜色背景 +
成功
+ ``` + +4. **开发工具** (`mock-devtools/`):规则可以放宽 + +--- + +## 自动检查 + +运行主题检查: + +```bash +pnpm theme:check +``` + +检查规则: + +| 规则 | 严重性 | 说明 | +|------|--------|------| +| `no-bg-as-text` | Error | 禁止将背景色用作文字色 | +| `text-white-on-semantic-bg` | Error | 禁止在语义背景色上使用 text-white | +| `orphan-foreground` | Warning | foreground 颜色应有配套背景 | +| `bg-muted-no-text-color` | Warning | bg-muted 需要明确的文字颜色 | +| `missing-dark-variant` | Warning | 硬编码灰色应有 dark: 变体 | + +--- + +## 相关文档 + +- [主题配色系统](./theme-colors.md) - 配色对的详细说明 +- [shadcn/ui 主题文档](https://ui.shadcn.com/docs/theming) diff --git a/package.json b/package.json index 49809412..cbed76de 100644 --- a/package.json +++ b/package.json @@ -48,8 +48,10 @@ "i18n:check": "turbo run i18n:run --", "i18n:run": "bun scripts/i18n-check.ts", "i18n:validate": "bun scripts/i18n-validate.ts", + "theme:check": "turbo run theme:run --", + "theme:run": "bun scripts/theme-check.ts", "agent": "bun scripts/agent/cli.ts", - "check": "turbo run typecheck:run test:run i18n:run" + "check": "turbo run typecheck:run test:run i18n:run theme:run" }, "dependencies": { "@base-ui/react": "^1.0.0", diff --git a/scripts/theme-check.ts b/scripts/theme-check.ts new file mode 100644 index 00000000..9419f414 --- /dev/null +++ b/scripts/theme-check.ts @@ -0,0 +1,493 @@ +#!/usr/bin/env bun +/** + * Theme (Dark Mode) Lint Script + * + * Validates that components follow the dark mode best practices: + * 1. Color pairs must be used together (bg-xxx with text-xxx-foreground) + * 2. Hardcoded colors should have dark: variants + * 3. Don't use background colors as text colors + * + * Usage: + * pnpm theme:check # Check for theme issues + * pnpm theme:check --fix # Auto-fix some issues (experimental) + * pnpm theme:check --verbose # Show all checked files + */ + +import { readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs' +import { resolve, join, relative } from 'node:path' + +// ==================== Configuration ==================== + +const ROOT = resolve(import.meta.dirname, '..') +const SRC_DIR = join(ROOT, 'src') + +// Files/directories to skip +const SKIP_PATTERNS = [ + '/node_modules/', + '/.git/', // Note: /.git/ not .git to avoid matching .git-worktree + '/dist/', + '/coverage/', + '/__tests__/', + '.stories.', + '.test.', + '/mock-devtools/', // Dev tools can have looser rules +] + +// Contexts where hardcoded colors are acceptable +const ACCEPTABLE_CONTEXTS = [ + 'bg-black', // Scanner/camera overlay + 'bg-white/20', // Semi-transparent on gradient backgrounds + 'bg-white/10', + 'bg-white/30', + 'text-white/80', // Semi-transparent white on colored backgrounds + 'text-white/50', + 'bg-gradient-', // Gradient backgrounds +] + +// Backgrounds that allow text-white (these don't change much in dark mode) +const ALLOWS_TEXT_WHITE = [ + 'bg-gradient-', + 'bg-green-', + 'bg-red-', + 'bg-blue-', + 'bg-orange-', + 'bg-black', +] + +// ==================== Colors ==================== + +const colors = { + reset: '\x1b[0m', + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + cyan: '\x1b[36m', + dim: '\x1b[2m', + bold: '\x1b[1m', +} + +const log = { + info: (msg: string) => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`), + success: (msg: string) => console.log(`${colors.green}✓${colors.reset} ${msg}`), + warn: (msg: string) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`), + error: (msg: string) => console.log(`${colors.red}✗${colors.reset} ${msg}`), + step: (msg: string) => console.log(`\n${colors.cyan}▸${colors.reset} ${colors.cyan}${msg}${colors.reset}`), + dim: (msg: string) => console.log(`${colors.dim} ${msg}${colors.reset}`), +} + +// ==================== Types ==================== + +interface Issue { + file: string + line: number + column: number + rule: string + message: string + severity: 'error' | 'warning' + suggestion?: string +} + +// ==================== Rules ==================== + +/** + * Rule 1: Don't use background color tokens as text colors + * e.g., text-secondary, text-accent (these are background colors) + */ +function checkBackgroundAsText(content: string, file: string): Issue[] { + const issues: Issue[] = [] + const lines = content.split('\n') + + // Background color tokens that should NOT be used with text- + const bgOnlyTokens = ['secondary', 'accent', 'muted', 'card', 'popover', 'sidebar'] + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + + for (const token of bgOnlyTokens) { + // Match text-{token} but NOT text-{token}-foreground + const regex = new RegExp(`text-${token}(?!-foreground)\\b`, 'g') + let match: RegExpExecArray | null + + while ((match = regex.exec(line)) !== null) { + issues.push({ + file, + line: i + 1, + column: match.index + 1, + rule: 'no-bg-as-text', + message: `'text-${token}' uses a background color as text color`, + severity: 'error', + suggestion: token === 'secondary' || token === 'muted' + ? `Use 'text-muted-foreground' for secondary text, or 'bg-${token} text-${token}-foreground' for buttons` + : `Use 'text-${token}-foreground' with 'bg-${token}' background`, + }) + } + } + } + + return issues +} + +/** + * Rule 2: Foreground colors (except muted-foreground) should have matching background + * Note: text-muted-foreground can be used standalone for secondary text + * Skip peer/group conditional styles as they pair with conditional backgrounds + */ +function checkOrphanForeground(content: string, file: string): Issue[] { + const issues: Issue[] = [] + const lines = content.split('\n') + + // These foreground colors need their background pair + // Note: muted-foreground is excluded - it's designed for standalone use + const foregroundTokens = ['primary', 'secondary', 'destructive', 'accent', 'card', 'popover'] + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + + for (const token of foregroundTokens) { + // Look for text-{token}-foreground (including variants like /70) + const fgMatch = line.match(new RegExp(`text-${token}-foreground`)) + if (!fgMatch) continue + + // Skip conditional foreground styles (peer-*, group-*, data-*) + // These pair with conditional bg-* styles + if (new RegExp(`(peer-|group-|data-)\\S*text-${token}-foreground`).test(line)) { + continue + } + + // Check if the same line or nearby context has bg-{token} + // Expand context to 5 lines before and after + const context = lines.slice(Math.max(0, i - 5), Math.min(lines.length, i + 6)).join(' ') + + // Check for bg-{token} including conditional variants + const hasBgToken = new RegExp(`bg-${token}|peer-\\S*:bg-${token}|data-\\S*:bg-${token}|group-\\S*:bg-${token}`).test(context) + + if (!hasBgToken) { + issues.push({ + file, + line: i + 1, + column: fgMatch.index! + 1, + rule: 'orphan-foreground', + message: `'text-${token}-foreground' without visible 'bg-${token}' - verify background is set`, + severity: 'warning', + suggestion: `Ensure 'bg-${token}' is set on this element or a parent`, + }) + } + } + } + + return issues +} + +/** + * Rule 3: Hardcoded gray colors should have dark: variants + * e.g., bg-gray-100 should have dark:bg-gray-800 + */ +function checkMissingDarkVariant(content: string, file: string): Issue[] { + const issues: Issue[] = [] + const lines = content.split('\n') + + // Patterns that need dark: variants + const needsDarkVariant = [ + { pattern: /\bbg-gray-(\d+)\b/g, type: 'bg' }, + { pattern: /\bbg-slate-(\d+)\b/g, type: 'bg' }, + { pattern: /\bbg-zinc-(\d+)\b/g, type: 'bg' }, + { pattern: /\btext-gray-(\d+)\b/g, type: 'text' }, + { pattern: /\bborder-gray-(\d+)\b/g, type: 'border' }, + ] + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + + for (const { pattern, type } of needsDarkVariant) { + let match: RegExpExecArray | null + const patternCopy = new RegExp(pattern.source, pattern.flags) + + while ((match = patternCopy.exec(line)) !== null) { + const fullMatch = match[0] + const shade = match[1] + + // Skip if acceptable context + if (ACCEPTABLE_CONTEXTS.some((ctx) => line.includes(ctx))) { + continue + } + + // Check if there's a dark: variant for this class + const context = lines.slice(Math.max(0, i - 1), Math.min(lines.length, i + 2)).join(' ') + const darkPattern = new RegExp(`dark:${type}-(?:gray|slate|zinc)-\\d+`) + + if (!darkPattern.test(context)) { + issues.push({ + file, + line: i + 1, + column: match.index + 1, + rule: 'missing-dark-variant', + message: `'${fullMatch}' should have a dark: variant`, + severity: 'warning', + suggestion: `Add 'dark:${type}-gray-${invertShade(shade)}' or use semantic colors like 'bg-muted'`, + }) + } + } + } + } + + return issues +} + +/** + * Rule 4: bg-primary/destructive should use text-xxx-foreground, not text-white + * In dark mode, primary-foreground changes to dark color while text-white stays white + */ +function checkTextWhiteOnSemanticBg(content: string, file: string): Issue[] { + const issues: Issue[] = [] + const lines = content.split('\n') + + // Semantic backgrounds that need their foreground pair, not text-white + const semanticBgs = ['primary', 'destructive', 'secondary'] + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + + // Check if line has text-white + if (!line.includes('text-white')) continue + + // Skip if text-white is with acceptable backgrounds + if (ALLOWS_TEXT_WHITE.some((bg) => line.includes(bg))) continue + + // Check if this line or nearby context has a semantic background + const context = lines.slice(Math.max(0, i - 2), Math.min(lines.length, i + 3)).join(' ') + + for (const bg of semanticBgs) { + if (context.includes(`bg-${bg}`) && !context.includes(`text-${bg}-foreground`)) { + issues.push({ + file, + line: i + 1, + column: line.indexOf('text-white') + 1, + rule: 'text-white-on-semantic-bg', + message: `'text-white' with 'bg-${bg}' - use 'text-${bg}-foreground' instead`, + severity: 'error', + suggestion: `Replace 'text-white' with 'text-${bg}-foreground' for proper dark mode support`, + }) + break // Only report once per line + } + } + } + + return issues +} + +/** + * Rule 5: bg-muted without text color may be invisible in dark mode + * Only warn if the element seems to have direct text content + */ +function checkBgMutedWithoutText(content: string, file: string): Issue[] { + const issues: Issue[] = [] + const lines = content.split('\n') + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + + // Check if line has bg-muted (not bg-muted/xx which is semi-transparent) + const bgMutedMatch = line.match(/\bbg-muted\b(?!\/)/) + if (!bgMutedMatch) continue + + // Skip if it's hover/focus/active state only + if (/hover:bg-muted|focus:bg-muted|active:bg-muted/.test(line) && !/\sbg-muted\b/.test(line)) { + continue + } + + // Check if there's any text color in the same className context (within 3 lines) + const context = lines.slice(Math.max(0, i - 1), Math.min(lines.length, i + 4)).join(' ') + + // Look for text-* colors + const hasTextColor = /\btext-(?:muted-foreground|foreground|primary|destructive|green-|red-|blue-|gray-|slate-|zinc-|white)/.test(context) + + // Also check if it's used purely as decorative/layout (no text content expected) + const isDecorative = /(?:size-|w-|h-)\d+.*(?:rounded|aspect)|flex.*items-center.*justify-center.*(?:rounded|size-)/.test(context) + + // Skip if has text color or is decorative + if (hasTextColor || isDecorative) continue + + // Skip if it's a container with child elements that likely have their own text colors + const isContainer = /className.*bg-muted.*>\s*$| = { + '50': '900', + '100': '800', + '200': '700', + '300': '600', + '400': '500', + '500': '400', + '600': '300', + '700': '200', + '800': '100', + '900': '50', + } + return shadeMap[shade] || '800' +} + +function shouldSkipFile(filePath: string): boolean { + return SKIP_PATTERNS.some((pattern) => filePath.includes(pattern)) +} + +function getAllFiles(dir: string, files: string[] = []): string[] { + let entries: string[] + try { + entries = readdirSync(dir) + } catch { + return files + } + + for (const entry of entries) { + const fullPath = join(dir, entry) + + if (shouldSkipFile(fullPath)) continue + + let stat + try { + stat = statSync(fullPath) + } catch { + continue + } + + if (stat.isDirectory()) { + getAllFiles(fullPath, files) + } else if (entry.endsWith('.tsx') || entry.endsWith('.jsx')) { + files.push(fullPath) + } + } + + return files +} + +// ==================== Main Logic ==================== + +async function main() { + const args = process.argv.slice(2) + const verbose = args.includes('--verbose') + + console.log(` +${colors.cyan}╔════════════════════════════════════════╗ +║ Theme (Dark Mode) Lint ║ +╚════════════════════════════════════════╝${colors.reset} +`) + + const files = getAllFiles(SRC_DIR) + log.info(`Checking ${files.length} files...`) + + const allIssues: Issue[] = [] + + for (const file of files) { + const content = readFileSync(file, 'utf-8') + const relPath = relative(ROOT, file) + + const issues = [ + ...checkBackgroundAsText(content, relPath), + ...checkOrphanForeground(content, relPath), + ...checkMissingDarkVariant(content, relPath), + ...checkTextWhiteOnSemanticBg(content, relPath), + ...checkBgMutedWithoutText(content, relPath), + ...checkSemanticColors(content, relPath), + ] + + allIssues.push(...issues) + + if (verbose && issues.length === 0) { + log.success(relPath) + } + } + + // Report results + log.step('Results') + + if (allIssues.length === 0) { + log.success('No theme issues found!') + console.log(` +${colors.green}✓ All ${files.length} files follow dark mode best practices${colors.reset} +`) + process.exit(0) + } + + // Group by file + const byFile = new Map() + for (const issue of allIssues) { + if (!byFile.has(issue.file)) { + byFile.set(issue.file, []) + } + byFile.get(issue.file)!.push(issue) + } + + const errorCount = allIssues.filter((i) => i.severity === 'error').length + const warningCount = allIssues.filter((i) => i.severity === 'warning').length + + for (const [file, issues] of byFile) { + console.log(`\n${colors.bold}${file}${colors.reset}`) + + for (const issue of issues) { + const icon = issue.severity === 'error' ? colors.red + '✗' : colors.yellow + '⚠' + console.log(` ${icon}${colors.reset} Line ${issue.line}: ${issue.message}`) + if (issue.suggestion) { + log.dim(` → ${issue.suggestion}`) + } + } + } + + console.log(` +${colors.bold}Summary:${colors.reset} + ${colors.red}Errors: ${errorCount}${colors.reset} + ${colors.yellow}Warnings: ${warningCount}${colors.reset} +`) + + // Exit with error if there are errors (warnings are OK for CI) + if (errorCount > 0) { + log.info(`See docs/white-book/02-设计篇/02-视觉设计/theme-colors.md for guidance`) + process.exit(1) + } + + log.success('No blocking errors (warnings can be addressed later)') +} + +main().catch((error) => { + log.error(`Check failed: ${error.message}`) + console.error(error) + process.exit(1) +}) diff --git a/src/components/layout/tab-bar.tsx b/src/components/layout/tab-bar.tsx index d38e59d8..92c590e6 100644 --- a/src/components/layout/tab-bar.tsx +++ b/src/components/layout/tab-bar.tsx @@ -45,7 +45,7 @@ export function TabBar({ items, activeId, onTabChange, className }: TabBarProps) {isActive && item.activeIcon ? item.activeIcon : item.icon} {item.badge !== undefined && ( - + {typeof item.badge === 'number' && item.badge > 99 ? '99+' : item.badge} )} diff --git a/src/components/onboarding/create-wallet-success.tsx b/src/components/onboarding/create-wallet-success.tsx index b4857d66..a2577b71 100644 --- a/src/components/onboarding/create-wallet-success.tsx +++ b/src/components/onboarding/create-wallet-success.tsx @@ -71,7 +71,7 @@ export function CreateWalletSuccess({ type="button" onClick={onBackup} className={cn( - 'flex w-full items-center justify-center gap-2 rounded-full py-3 font-medium text-white transition-colors', + 'flex w-full items-center justify-center gap-2 rounded-full py-3 font-medium text-primary-foreground transition-colors', 'bg-primary hover:bg-primary/90', )} > @@ -87,7 +87,7 @@ export function CreateWalletSuccess({ 'flex w-full items-center justify-center gap-2 rounded-full py-3 font-medium transition-colors', skipBackup && onBackup ? 'border-border text-foreground hover:bg-muted border' - : 'bg-primary hover:bg-primary/90 text-white', + : 'bg-primary hover:bg-primary/90 text-primary-foreground', )} > {skipBackup && onBackup ? t('create.success.backupLater') : t('create.success.enterWallet')} diff --git a/src/components/onboarding/import-wallet-success.tsx b/src/components/onboarding/import-wallet-success.tsx index f178e4dc..f72a0d13 100644 --- a/src/components/onboarding/import-wallet-success.tsx +++ b/src/components/onboarding/import-wallet-success.tsx @@ -50,7 +50,7 @@ export function ImportWalletSuccess({ onClick={onEnterWallet} className={cn( 'flex w-full items-center justify-center gap-2 rounded-full py-3 font-medium transition-colors', - 'bg-primary hover:bg-primary/90 text-white', + 'bg-primary hover:bg-primary/90 text-primary-foreground', )} > {t('import.success.enterWallet')} diff --git a/src/components/onboarding/recover-wallet-form.tsx b/src/components/onboarding/recover-wallet-form.tsx index 1a370ce8..363199da 100644 --- a/src/components/onboarding/recover-wallet-form.tsx +++ b/src/components/onboarding/recover-wallet-form.tsx @@ -250,7 +250,7 @@ export function RecoverWalletForm({ onSubmit, isSubmitting = false, className }: data-testid="continue-button" disabled={!validation.isValid || isSubmitting} className={cn( - 'flex w-full items-center justify-center gap-2 rounded-full py-3 font-medium text-white transition-colors', + 'flex w-full items-center justify-center gap-2 rounded-full py-3 font-medium text-primary-foreground transition-colors', 'bg-primary hover:bg-primary/90', 'disabled:cursor-not-allowed disabled:opacity-50', )} diff --git a/src/components/transaction/tx-status-display.tsx b/src/components/transaction/tx-status-display.tsx index f2be31b8..e2b5e4b3 100644 --- a/src/components/transaction/tx-status-display.tsx +++ b/src/components/transaction/tx-status-display.tsx @@ -217,7 +217,7 @@ export function TxStatusDisplay({ onClick={onDone} data-testid="tx-status-done-button" className={cn( - "w-full rounded-full py-3 font-medium text-white transition-colors", + "w-full rounded-full py-3 font-medium text-primary-foreground transition-colors", "bg-primary hover:bg-primary/90" )} > @@ -256,7 +256,7 @@ export function TxStatusDisplay({ onClick={onRetry} data-testid="tx-status-retry-button" className={cn( - "w-full max-w-xs rounded-full py-3 font-medium text-white transition-colors", + "w-full max-w-xs rounded-full py-3 font-medium text-primary-foreground transition-colors", "bg-primary hover:bg-primary/90" )} > diff --git a/src/components/transfer/send-result.tsx b/src/components/transfer/send-result.tsx index 30b1d7ff..d197d95a 100644 --- a/src/components/transfer/send-result.tsx +++ b/src/components/transfer/send-result.tsx @@ -143,7 +143,7 @@ export function SendResult({ @@ -156,7 +156,7 @@ export function SendResult({ className={cn( 'flex items-center justify-center gap-2 rounded-full py-3 font-medium transition-colors', isSuccess || isPending - ? 'bg-primary hover:bg-primary/90 text-white' + ? 'bg-primary hover:bg-primary/90 text-primary-foreground' : 'border-border hover:bg-muted border', )} > diff --git a/src/pages/address-book/index.tsx b/src/pages/address-book/index.tsx index 202776a9..c18d6a56 100644 --- a/src/pages/address-book/index.tsx +++ b/src/pages/address-book/index.tsx @@ -162,7 +162,7 @@ export function AddressBookPage() {