feat: update
This commit is contained in:
232
components/ThemeDemo.tsx
Normal file
232
components/ThemeDemo.tsx
Normal 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',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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
16
components/index.ts
Normal 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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { useColorScheme } from 'react-native';
|
||||
@@ -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';
|
||||
}
|
||||
Reference in New Issue
Block a user