feat: update

This commit is contained in:
2025-11-06 16:37:01 +08:00
parent c0d54b8513
commit 855f289579
59 changed files with 3398 additions and 572 deletions

232
components/ThemeDemo.tsx Normal file
View File

@@ -0,0 +1,232 @@
/**
* 主题演示组件
*
* 展示所有主题颜色和组件在不同主题下的效果
*/
import React from 'react';
import { StyleSheet, View, Text, TouchableOpacity, ScrollView } from 'react-native';
import { ThemedText, ThemedView, useThemeColor } from './Themed';
import { useTheme, useSettingsActions } from '@/stores';
import { useHaptics } from '@/hooks';
export function ThemeDemo() {
const theme = useTheme();
const { setTheme } = useSettingsActions();
const haptics = useHaptics();
// 获取主题颜色
const primary = useThemeColor({}, 'primary');
const secondary = useThemeColor({}, 'secondary');
const success = useThemeColor({}, 'success');
const warning = useThemeColor({}, 'warning');
const error = useThemeColor({}, 'error');
const info = useThemeColor({}, 'info');
const border = useThemeColor({}, 'border');
const card = useThemeColor({}, 'card');
const textSecondary = useThemeColor({}, 'textSecondary');
const handleThemeChange = (newTheme: 'light' | 'dark' | 'auto') => {
haptics.selection();
console.log('🎨 Changing theme to:', newTheme);
setTheme(newTheme);
console.log('🎨 Theme colors after change:', { primary, secondary, background: card });
};
return (
<ThemedView style={styles.container}>
<ScrollView>
{/* 主题切换器 */}
<View style={styles.section}>
<ThemedText type="subtitle"></ThemedText>
<View style={styles.themeButtons}>
<TouchableOpacity
style={[
styles.themeButton,
{ borderColor: border },
theme === 'light' && { backgroundColor: primary },
]}
onPress={() => handleThemeChange('light')}
>
<Text style={[styles.themeButtonText, theme === 'light' && styles.activeButtonText]}>
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.themeButton,
{ borderColor: border },
theme === 'dark' && { backgroundColor: primary },
]}
onPress={() => handleThemeChange('dark')}
>
<Text style={[styles.themeButtonText, theme === 'dark' && styles.activeButtonText]}>
🌙
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.themeButton,
{ borderColor: border },
theme === 'auto' && { backgroundColor: primary },
]}
onPress={() => handleThemeChange('auto')}
>
<Text style={[styles.themeButtonText, theme === 'auto' && styles.activeButtonText]}>
🔄
</Text>
</TouchableOpacity>
</View>
<ThemedText style={styles.hint}>
: {theme === 'light' ? '浅色' : theme === 'dark' ? '深色' : '自动'}
</ThemedText>
</View>
{/* 文本样式 */}
<View style={styles.section}>
<ThemedText type="subtitle"></ThemedText>
<ThemedText type="title"> (Title)</ThemedText>
<ThemedText type="subtitle"> (Subtitle)</ThemedText>
<ThemedText type="defaultSemiBold"> (SemiBold)</ThemedText>
<ThemedText type="default"> (Default)</ThemedText>
<ThemedText type="link"> (Link)</ThemedText>
</View>
{/* 颜色展示 */}
<View style={styles.section}>
<ThemedText type="subtitle"></ThemedText>
<View style={styles.colorGrid}>
<View style={styles.colorItem}>
<View style={[styles.colorBox, { backgroundColor: primary }]} />
<ThemedText style={styles.colorLabel}>Primary</ThemedText>
</View>
<View style={styles.colorItem}>
<View style={[styles.colorBox, { backgroundColor: secondary }]} />
<ThemedText style={styles.colorLabel}>Secondary</ThemedText>
</View>
<View style={styles.colorItem}>
<View style={[styles.colorBox, { backgroundColor: success }]} />
<ThemedText style={styles.colorLabel}>Success</ThemedText>
</View>
<View style={styles.colorItem}>
<View style={[styles.colorBox, { backgroundColor: warning }]} />
<ThemedText style={styles.colorLabel}>Warning</ThemedText>
</View>
<View style={styles.colorItem}>
<View style={[styles.colorBox, { backgroundColor: error }]} />
<ThemedText style={styles.colorLabel}>Error</ThemedText>
</View>
<View style={styles.colorItem}>
<View style={[styles.colorBox, { backgroundColor: info }]} />
<ThemedText style={styles.colorLabel}>Info</ThemedText>
</View>
</View>
</View>
{/* 卡片示例 */}
<View style={styles.section}>
<ThemedText type="subtitle"></ThemedText>
<View style={[styles.card, { backgroundColor: card, borderColor: border }]}>
<ThemedText type="defaultSemiBold"></ThemedText>
<ThemedText style={{ color: textSecondary }}>
</ThemedText>
</View>
</View>
{/* 按钮示例 */}
<View style={styles.section}>
<ThemedText type="subtitle"></ThemedText>
<TouchableOpacity style={[styles.button, { backgroundColor: primary }]}>
<Text style={styles.buttonText}>Primary Button</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.button, { backgroundColor: secondary }]}>
<Text style={styles.buttonText}>Secondary Button</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.button, { backgroundColor: success }]}>
<Text style={styles.buttonText}>Success Button</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.button, { backgroundColor: error }]}>
<Text style={styles.buttonText}>Error Button</Text>
</TouchableOpacity>
</View>
</ScrollView>
</ThemedView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
section: {
marginBottom: 24,
},
themeButtons: {
flexDirection: 'row',
gap: 12,
marginTop: 12,
},
themeButton: {
flex: 1,
paddingVertical: 12,
paddingHorizontal: 16,
borderRadius: 8,
borderWidth: 1,
alignItems: 'center',
},
themeButtonText: {
fontSize: 14,
fontWeight: '600',
},
activeButtonText: {
color: '#FFFFFF',
},
hint: {
marginTop: 8,
fontSize: 12,
opacity: 0.7,
},
colorGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 16,
marginTop: 12,
},
colorItem: {
alignItems: 'center',
width: 80,
},
colorBox: {
width: 60,
height: 60,
borderRadius: 8,
marginBottom: 8,
},
colorLabel: {
fontSize: 12,
textAlign: 'center',
},
card: {
padding: 16,
borderRadius: 12,
borderWidth: 1,
marginTop: 12,
},
button: {
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 8,
alignItems: 'center',
marginTop: 12,
},
buttonText: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '600',
},
});

View File

@@ -1,12 +1,14 @@
/**
* Learn more about Light and Dark modes:
* https://docs.expo.io/guides/color-schemes/
* 主题化组件
*
* 提供自动适配主题的 Text 和 View 组件
* 支持从 settingsStore 读取主题设置
*/
import { Text as DefaultText, View as DefaultView } from 'react-native';
import { Text as DefaultText, View as DefaultView, TextStyle } from 'react-native';
import Colors from '@/constants/Colors';
import { useColorScheme } from './useColorScheme';
import { useColorScheme } from '@/hooks/useTheme';
type ThemeProps = {
lightColor?: string;
@@ -16,6 +18,13 @@ type ThemeProps = {
export type TextProps = ThemeProps & DefaultText['props'];
export type ViewProps = ThemeProps & DefaultView['props'];
/**
* 获取主题颜色
*
* @param props - 包含 light 和 dark 颜色的对象
* @param colorName - Colors 中定义的颜色名称
* @returns 当前主题对应的颜色值
*/
export function useThemeColor(
props: { light?: string; dark?: string },
colorName: keyof typeof Colors.light & keyof typeof Colors.dark
@@ -30,6 +39,11 @@ export function useThemeColor(
}
}
/**
* 主题化 Text 组件
*
* 自动应用当前主题的文本颜色
*/
export function Text(props: TextProps) {
const { style, lightColor, darkColor, ...otherProps } = props;
const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
@@ -37,9 +51,77 @@ export function Text(props: TextProps) {
return <DefaultText style={[{ color }, style]} {...otherProps} />;
}
/**
* 主题化 View 组件
*
* 自动应用当前主题的背景颜色
*/
export function View(props: ViewProps) {
const { style, lightColor, darkColor, ...otherProps } = props;
const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
return <DefaultView style={[{ backgroundColor }, style]} {...otherProps} />;
}
/**
* 主题化 Text 组件(带类型)
*
* 支持不同的文本类型title, subtitle, defaultSemiBold, link
*/
export type ThemedTextProps = TextProps & {
type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link';
};
export function ThemedText({ style, type = 'default', ...rest }: ThemedTextProps) {
const color = useThemeColor({}, 'text');
const linkColor = useThemeColor({}, 'tint');
const typeStyles: Record<string, TextStyle> = {
default: {
fontSize: 16,
lineHeight: 24,
},
defaultSemiBold: {
fontSize: 16,
lineHeight: 24,
fontWeight: '600',
},
title: {
fontSize: 32,
fontWeight: 'bold',
lineHeight: 40,
},
subtitle: {
fontSize: 20,
fontWeight: '600',
lineHeight: 28,
},
link: {
fontSize: 16,
lineHeight: 24,
color: linkColor,
},
};
return (
<Text
style={[
{ color },
typeStyles[type],
style,
]}
{...rest}
/>
);
}
/**
* 主题化 View 组件
*/
export type ThemedViewProps = ViewProps;
export function ThemedView({ style, ...otherProps }: ThemedViewProps) {
const backgroundColor = useThemeColor({}, 'background');
return <DefaultView style={[{ backgroundColor }, style]} {...otherProps} />;
}

16
components/index.ts Normal file
View File

@@ -0,0 +1,16 @@
/**
* Components 统一导出
*/
// 主题组件
export { ThemedText, ThemedView, Text, View, useThemeColor } from './Themed';
export type { ThemedTextProps, ThemedViewProps, TextProps, ViewProps } from './Themed';
// 主题演示
export { ThemeDemo } from './ThemeDemo';
// 工具组件
export { ExternalLink } from './ExternalLink';
export { MonoText } from './StyledText';

View File

@@ -1,4 +0,0 @@
// This function is web-only as native doesn't currently support server (or build-time) rendering.
export function useClientOnlyValue<S, C>(server: S, client: C): S | C {
return client;
}

View File

@@ -1,12 +0,0 @@
import React from 'react';
// `useEffect` is not invoked during server rendering, meaning
// we can use this to determine if we're on the server or not.
export function useClientOnlyValue<S, C>(server: S, client: C): S | C {
const [value, setValue] = React.useState<S | C>(server);
React.useEffect(() => {
setValue(client);
}, [client]);
return value;
}

View File

@@ -1 +0,0 @@
export { useColorScheme } from 'react-native';

View File

@@ -1,8 +0,0 @@
// NOTE: The default React Native styling doesn't support server rendering.
// Server rendered styles should not change between the first render of the HTML
// and the first render on the client. Typically, web developers will use CSS media queries
// to render different styles on the client and server, these aren't directly supported in React Native
// but can be achieved using a styling library like Nativewind.
export function useColorScheme() {
return 'light';
}