feat(Theme): enhance theming and UI consistency across components
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 8m15s
Frontend CI / ota-android (push) Successful in 10m56s
Frontend CI / build-android-apk (push) Successful in 1h3m22s

- Updated app.json to set userInterfaceStyle to automatic for improved theme adaptability.
- Refactored layout components to utilize useAppColors for dynamic theming, ensuring consistent color usage.
- Introduced SystemChrome component to manage system UI background color based on theme.
- Enhanced TabsLayout, ProfileStackLayout, and other components to leverage new theming structure.
- Improved QRCodeScanner, SearchBar, and CommentItem styles to align with the updated theme system.
- Consolidated styles in SystemMessageItem and TabBar for better maintainability and visual coherence.
This commit is contained in:
lafay
2026-03-25 05:16:54 +08:00
parent 90d834695f
commit 4ee3079b9f
86 changed files with 6777 additions and 5890 deletions

View File

@@ -1,183 +1,28 @@
/**
* 胡萝卜BBS主题系统
* 基于胡萝卜橙主色调的设计系统
* 胡萝卜BBS 主题明暗色板、Paper 主题、设计 token
* 新代码请优先使用 useAppColors();静态 colors 为浅色快照,与深色模式不同步。
*/
// ==================== 颜色系统 ====================
export const colors = {
primary: {
main: '#FF6B35', // 橙色(主色)
light: '#FFB733',
dark: '#CC8400',
contrast: '#FFFFFF',
},
secondary: {
main: '#4CAF50',
light: '#80E27E',
dark: '#087F23',
},
neutral: {
main: '#6B7280', // 中性灰(辅助色)
light: '#9CA3AF',
dark: '#374151',
bg: '#F3F4F6', // 浅灰背景
bgDark: '#1F2937', // 深灰背景
},
accent: {
main: '#E57373', // 低饱和红(点缀色)
light: '#FFCDD2',
dark: '#C62828',
},
background: {
default: '#F5F5F5',
paper: '#FFFFFF',
disabled: '#E0E0E0',
},
text: {
primary: '#212121',
secondary: '#757575',
disabled: '#9E9E9E',
hint: '#BDBDBD',
inverse: '#FFFFFF',
},
divider: '#E0E0E0',
error: {
main: '#F44336',
light: '#FF7961',
dark: '#D32F2F',
},
success: {
main: '#4CAF50',
light: '#80E27E',
dark: '#087F23',
},
warning: {
main: '#FF9800',
light: '#FFB74D',
dark: '#F57C00',
},
info: {
main: '#2196F3',
light: '#64B5F6',
dark: '#1976D2',
},
};
export { lightColors, darkColors, type AppColors } from './palettes';
export { buildPaperTheme } from './paperTheme';
export * from './tokens';
// ==================== 字体系统 ====================
export const fontSizes = {
xs: 10,
sm: 12,
md: 14,
lg: 16,
xl: 18,
'2xl': 20,
'3xl': 24,
'4xl': 30,
};
export {
useAppColors,
useThemePreference,
useResolvedColorScheme,
useSetThemePreference,
usePaperThemeFromStore,
ThemeBootstrap,
useThemeStore,
type ThemePreference,
type ResolvedScheme,
} from '../stores/themeStore';
// ==================== 间距系统4px基准====================
export const spacing = {
xs: 4,
sm: 8,
md: 12,
lg: 16,
xl: 20,
'2xl': 24,
'3xl': 32,
'4xl': 40,
'5xl': 48,
'6xl': 56,
};
import { lightColors } from './palettes';
import { buildPaperTheme } from './paperTheme';
// ==================== 圆角系统 ====================
export const borderRadius = {
sm: 4,
md: 8,
lg: 12,
xl: 16,
'2xl': 24,
full: 9999,
};
/** 浅色静态快照;随主题切换请使用 useAppColors() */
export const colors = lightColors;
// ==================== 阴影系统 ====================
export const shadows = {
sm: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.18,
shadowRadius: 1.0,
elevation: 1
},
md: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.22,
shadowRadius: 2.22,
elevation: 3
},
lg: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 4.65,
elevation: 6
},
};
// ==================== 导出主题对象 ====================
export const theme = {
colors,
fontSizes,
spacing,
borderRadius,
shadows
};
export type Theme = typeof theme;
// ==================== 主题配置用于React Native Paper====================
export const paperTheme = {
colors: {
primary: colors.primary.main,
primaryContainer: colors.primary.light,
secondary: colors.secondary.main,
secondaryContainer: colors.secondary.light,
tertiary: colors.info.main,
tertiaryContainer: colors.info.light,
surface: colors.background.paper,
surfaceVariant: colors.background.default,
surfaceDisabled: colors.background.disabled,
background: colors.background.default,
error: colors.error.main,
errorContainer: colors.error.light,
onPrimary: colors.primary.contrast,
onPrimaryContainer: colors.primary.dark,
onSecondary: colors.text.inverse,
onSecondaryContainer: colors.secondary.dark,
onTertiary: colors.text.inverse,
onTertiaryContainer: colors.info.dark,
onSurface: colors.text.primary,
onSurfaceVariant: colors.text.secondary,
onSurfaceDisabled: colors.text.disabled,
onError: colors.text.inverse,
onErrorContainer: colors.error.dark,
onBackground: colors.text.primary,
outline: colors.divider,
outlineVariant: colors.divider,
inverseSurface: colors.text.primary,
inverseOnSurface: colors.text.inverse,
inversePrimary: colors.primary.light,
shadow: '#000000',
scrim: '#000000',
backdrop: 'rgba(0, 0, 0, 0.5)',
elevation: {
level0: 'transparent',
level1: colors.background.paper,
level2: colors.background.paper,
level3: colors.background.paper,
level4: colors.background.paper,
level5: colors.background.paper,
},
},
roundness: borderRadius.md,
};
export const paperTheme = buildPaperTheme(lightColors, false);

185
src/theme/palettes.ts Normal file
View File

@@ -0,0 +1,185 @@
/**
* 浅色 / 深色色板与聊天场景语义色
*/
export const lightColors = {
primary: {
main: '#FF6B35',
light: '#FFB733',
dark: '#CC8400',
contrast: '#FFFFFF',
},
secondary: {
main: '#4CAF50',
light: '#80E27E',
dark: '#087F23',
},
neutral: {
main: '#6B7280',
light: '#9CA3AF',
dark: '#374151',
bg: '#F3F4F6',
bgDark: '#1F2937',
},
accent: {
main: '#E57373',
light: '#FFCDD2',
dark: '#C62828',
},
background: {
default: '#F5F5F5',
paper: '#FFFFFF',
disabled: '#E0E0E0',
},
text: {
primary: '#212121',
secondary: '#757575',
disabled: '#9E9E9E',
hint: '#BDBDBD',
inverse: '#FFFFFF',
},
divider: '#E0E0E0',
error: {
main: '#F44336',
light: '#FF7961',
dark: '#D32F2F',
},
success: {
main: '#4CAF50',
light: '#80E27E',
dark: '#087F23',
},
warning: {
main: '#FF9800',
light: '#FFB74D',
dark: '#F57C00',
},
info: {
main: '#2196F3',
light: '#64B5F6',
dark: '#1976D2',
},
chat: {
screen: '#F0F2F5',
card: '#FFFFFF',
surfaceRaised: '#F7F8FA',
surfaceMuted: '#F5F7FA',
surfaceInput: '#F5F5F5',
border: '#E8E8E8',
borderLight: '#ECEFF3',
borderHairline: '#F0F0F0',
textPrimary: '#1A1A1A',
textSecondary: '#8E8E93',
textTertiary: '#666666',
textPlaceholder: '#AAAAAA',
textMuted: '#999999',
link: '#4A88C7',
success: '#34C759',
danger: '#FF3B30',
bubbleOutgoing: '#E8E8E8',
bubbleIncoming: '#FFFFFF',
replyTint: '#DFF2FF',
replyTintActive: '#CFEAFF',
replyBorder: '#7FB6E6',
menuHighlight: '#EEF5FC',
iconMuted: '#BBBBBB',
iconSoft: '#C8C8C8',
sheetGrip: '#E0E0E0',
warningBg: '#FFF5F5',
tipBg: '#FFF9E6',
overlayQuote: '#F0F7FF',
shadow: '#000000',
},
} as const;
export type AppColors = typeof lightColors;
export const darkColors = {
primary: {
main: '#FF8A50',
light: '#FFB733',
dark: '#E85D2A',
contrast: '#FFFFFF',
},
secondary: {
main: '#66BB6A',
light: '#81C784',
dark: '#388E3C',
},
neutral: {
main: '#9CA3AF',
light: '#D1D5DB',
dark: '#6B7280',
bg: '#374151',
bgDark: '#111827',
},
accent: {
main: '#EF9A9A',
light: '#FFCDD2',
dark: '#E57373',
},
background: {
default: '#121212',
paper: '#1E1E1E',
disabled: '#2C2C2C',
},
text: {
primary: '#ECECEC',
secondary: '#A8A8A8',
disabled: '#6E6E6E',
hint: '#888888',
inverse: '#121212',
},
divider: '#333333',
error: {
main: '#EF5350',
light: '#FF867C',
dark: '#C62828',
},
success: {
main: '#66BB6A',
light: '#81C784',
dark: '#388E3C',
},
warning: {
main: '#FFA726',
light: '#FFCC80',
dark: '#F57C00',
},
info: {
main: '#42A5F5',
light: '#90CAF9',
dark: '#1976D2',
},
chat: {
screen: '#0A0A0A',
card: '#1C1C1E',
surfaceRaised: '#2C2C2E',
surfaceMuted: '#252528',
surfaceInput: '#2A2A2C',
border: '#38383A',
borderLight: '#3A3A3C',
borderHairline: '#2C2C2E',
textPrimary: '#F2F2F7',
textSecondary: '#98989D',
textTertiary: '#AEAEB2',
textPlaceholder: '#636366',
textMuted: '#8E8E93',
link: '#5AC8FA',
success: '#32D74B',
danger: '#FF453A',
bubbleOutgoing: '#3A3A3C',
bubbleIncoming: '#2C2C2E',
replyTint: '#1A3A52',
replyTintActive: '#224060',
replyBorder: '#4A88C7',
menuHighlight: '#1C2A3A',
iconMuted: '#8E8E93',
iconSoft: '#636366',
sheetGrip: '#48484A',
warningBg: '#3A2222',
tipBg: '#3A3520',
overlayQuote: '#1A2838',
shadow: '#000000',
},
} as unknown as AppColors;

54
src/theme/paperTheme.ts Normal file
View File

@@ -0,0 +1,54 @@
import { MD3DarkTheme, MD3LightTheme, type MD3Theme } from 'react-native-paper';
import { borderRadius } from './tokens';
import type { AppColors } from './palettes';
export function buildPaperTheme(colors: AppColors, isDark: boolean): MD3Theme {
const base = isDark ? MD3DarkTheme : MD3LightTheme;
return {
...base,
colors: {
...base.colors,
primary: colors.primary.main,
primaryContainer: colors.primary.light,
secondary: colors.secondary.main,
secondaryContainer: colors.secondary.light,
tertiary: colors.info.main,
tertiaryContainer: colors.info.light,
surface: colors.background.paper,
surfaceVariant: colors.background.default,
surfaceDisabled: colors.background.disabled,
background: colors.background.default,
error: colors.error.main,
errorContainer: colors.error.light,
onPrimary: colors.primary.contrast,
onPrimaryContainer: colors.primary.dark,
onSecondary: colors.text.inverse,
onSecondaryContainer: colors.secondary.dark,
onTertiary: colors.text.inverse,
onTertiaryContainer: colors.info.dark,
onSurface: colors.text.primary,
onSurfaceVariant: colors.text.secondary,
onSurfaceDisabled: colors.text.disabled,
onError: colors.text.inverse,
onErrorContainer: colors.error.dark,
onBackground: colors.text.primary,
outline: colors.divider,
outlineVariant: colors.divider,
inverseSurface: colors.text.primary,
inverseOnSurface: colors.text.inverse,
inversePrimary: colors.primary.light,
shadow: '#000000',
scrim: '#000000',
backdrop: 'rgba(0, 0, 0, 0.5)',
elevation: {
level0: 'transparent',
level1: colors.background.paper,
level2: colors.background.paper,
level3: colors.background.paper,
level4: colors.background.paper,
level5: colors.background.paper,
},
},
roundness: borderRadius.md,
};
}

60
src/theme/tokens.ts Normal file
View File

@@ -0,0 +1,60 @@
/**
* 与明暗无关的设计 token
*/
export const fontSizes = {
xs: 10,
sm: 12,
md: 14,
lg: 16,
xl: 18,
'2xl': 20,
'3xl': 24,
'4xl': 30,
};
export const spacing = {
xs: 4,
sm: 8,
md: 12,
lg: 16,
xl: 20,
'2xl': 24,
'3xl': 32,
'4xl': 40,
'5xl': 48,
'6xl': 56,
};
export const borderRadius = {
sm: 4,
md: 8,
lg: 12,
xl: 16,
'2xl': 24,
full: 9999,
};
export const shadows = {
sm: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.18,
shadowRadius: 1.0,
elevation: 1,
},
md: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.22,
shadowRadius: 2.22,
elevation: 3,
},
lg: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 4.65,
elevation: 6,
},
};