feat: 登录+忘记密码

This commit is contained in:
jinyaqiu 2025-08-08 14:27:58 +08:00
parent 3d4ee1b210
commit 727ebcb483
13 changed files with 361 additions and 150 deletions

View File

@ -50,7 +50,9 @@
"expo-font",
{
"fonts": [
"./assets/font/english.otf"
"./assets/fonts/Quicksand.otf",
"./assets/fonts/SF-Pro.otf",
"./assets/fonts/Inter-Regular.otf"
]
}
],

View File

@ -60,7 +60,9 @@ export default function TabLayout() {
// 加载字体
const [loaded, error] = useFonts({
english: require('@/assets/font/english.otf'),
quicksand: require('@/assets/fonts/Quicksand.otf'),
sfPro: require('@/assets/fonts/SF-Pro.otf'),
inter: require('@/assets/fonts/Inter-Regular.otf'),
});
useEffect(() => {

View File

@ -74,9 +74,9 @@ const LoginScreen = () => {
keyboardShouldPersistTaps="handled"
bounces={false}
>
<ThemedView className="flex-1 bg-bgPrimary justify-end">
<ThemedView className="flex-1 justify-end" bgColor="bgPrimary">
<View style={{ width: "100%", alignItems: "center", marginTop: insets.top + 8, opacity: keyboardOffset === 0 ? 1 : 0 }}>
<ThemedText style={{ fontSize: 20, fontWeight: 'bold', color: "#fff" }}>{t('login:auth.login.titleText')}</ThemedText>
<ThemedText size="xl" weight="bold" color="textWhite">{t('login:auth.login.titleText')}</ThemedText>
</View>
<View className="flex-1">
<View
@ -105,7 +105,8 @@ const LoginScreen = () => {
</View>
</View>
<ThemedView
className="w-full bg-white pt-4 px-6 relative z-20 shadow-lg pb-5"
className="w-full pt-4 px-6 relative z-20 shadow-lg pb-5"
bgColor="textWhite"
style={{
borderTopLeftRadius: 50,
borderTopRightRadius: 50,
@ -120,7 +121,7 @@ const LoginScreen = () => {
>
{/* 错误提示 */}
<View className={`${error !== "123" ? 'opacity-100' : 'opacity-0'} w-full flex justify-center items-center text-primary-500 text-sm`}>
<ThemedText className="text-sm !text-textPrimary">
<ThemedText size='xxs' color='bgSecondary' type='inter'>
{error}
</ThemedText>
</View>
@ -161,22 +162,22 @@ const LoginScreen = () => {
return components[status as keyof typeof components] || components.login;
})()}
<View style={{ width: "100%", alignItems: "center", marginTop: 16 }}>
<View style={{ width: "100%", alignItems: "center", }}>
{status == 'login' || !status &&
<View className="flex-row justify-center mt-2 flex-wrap w-[85%] items-center">
<ThemedText style={{ fontSize: 11, color: "#FFB645" }}>
<ThemedText color='bgPrimary' size='xxs' type='inter'>
{status === 'login' || !status ? t('auth.agree.logintext', { ns: 'login' }) : t('auth.agree.singupText', { ns: 'login' })}
</ThemedText>
<TouchableOpacity onPress={() => { setModalVisible(true); setModalType('terms') }}>
<ThemedText style={{ fontSize: 11, color: "#FFB645", textDecorationLine: 'underline' }}>
<ThemedText color='bgPrimary' size='xxs' type='inter' style={{ textDecorationLine: 'underline' }}>
{t('auth.agree.terms', { ns: 'login' })}
</ThemedText>
</TouchableOpacity>
<ThemedText style={{ fontSize: 11, color: "#FFB645", flexWrap: 'wrap' }}>
<ThemedText color='bgPrimary' size='xxs' type='inter' className='flex-wrap'>
{t('auth.agree.join', { ns: 'login' })}
</ThemedText>
<TouchableOpacity onPress={() => { setModalVisible(true); setModalType('privacy') }}>
<ThemedText style={{ fontSize: 11, color: "#FFB645", textDecorationLine: 'underline' }}>
<ThemedText color='bgPrimary' size='xxs' type='inter' className='flex-wrap' style={{ textDecorationLine: 'underline' }}>
{t('auth.agree.privacyPolicy', { ns: 'login' })}
</ThemedText>
</TouchableOpacity>

Binary file not shown.

BIN
assets/fonts/SF-Pro.otf Normal file

Binary file not shown.

View File

@ -1,16 +1,26 @@
import { StyleProp, StyleSheet, Text, TextStyle, type TextProps } from 'react-native';
import { Fonts } from '@/constants/Fonts';
import { Colors } from '@/constants/Colors';
import { FontColor, Fonts } from '@/constants/Fonts';
import { useThemeColor } from '@/hooks/useThemeColor';
export type ThemeColor = keyof typeof Colors.light & keyof typeof Colors.dark;
export type ColorValue = `#${string}` | `rgb(${string})` | `rgba(${string})` | string;
export type ThemedTextProps = TextProps & {
lightColor?: string;
darkColor?: string;
type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link';
type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link' | 'sfPro' | 'inter';
weight?: 'regular' | 'medium' | 'semiBold' | 'bold';
size?: 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl';
size?: 'xxs' | 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl';
radius?: 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl';
color?: ThemeColor | FontColor | ColorValue;
};
export function isFontColorKey(key: string): key is FontColor {
return ['bgPrimary', 'bgSecondary', 'textPrimary', 'textSecondary', 'textThird', 'textWhite'].includes(key);
}
export function ThemedText({
style,
lightColor,
@ -18,20 +28,38 @@ export function ThemedText({
type = 'default',
weight = 'regular',
size,
radius,
color,
...rest
}: ThemedTextProps) {
const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
const themeColor = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
const textColor = (() => {
if (!color) return themeColor;
// 检查是否是主题颜色
const themeColors = Object.keys(Colors.light) as ThemeColor[];
if (themeColors.includes(color as ThemeColor)) {
return useThemeColor({ light: lightColor, dark: darkColor }, color as ThemeColor);
}
// 检查是否是 Fonts 中定义的颜色
if (isFontColorKey(color)) {
return Fonts[color as FontColor];
}
// 返回自定义颜色值
return color;
})();
const baseStyle: StyleProp<TextStyle> = {
fontFamily: Fonts.primary,
color,
fontWeight: Fonts[weight as keyof typeof Fonts] as TextStyle['fontWeight'],
fontFamily: Fonts.quicksand,
color: textColor,
fontWeight: Number(Fonts[weight as keyof typeof Fonts]) as TextStyle['fontWeight'],
};
if (size) {
baseStyle.fontSize = Fonts[size as keyof typeof Fonts];
}
return (
<Text
style={[
@ -41,6 +69,11 @@ export function ThemedText({
type === 'defaultSemiBold' ? styles.defaultSemiBold : undefined,
type === 'subtitle' ? styles.subtitle : undefined,
type === 'link' ? styles.link : undefined,
type === 'sfPro' ? styles.sfPro : undefined,
type === 'inter' ? styles.inter : undefined,
size && { fontSize: Number(Fonts[size as keyof typeof Fonts]) },
weight && { fontWeight: Number(Fonts[weight as keyof typeof Fonts]) as TextStyle['fontWeight'] },
color && { color: textColor },
style,
]}
{...rest}
@ -50,11 +83,12 @@ export function ThemedText({
const styles = StyleSheet.create({
default: {
fontSize: Fonts.base,
fontSize: Number(Fonts.base),
lineHeight: 24,
fontFamily: Fonts.quicksand,
},
defaultSemiBold: {
fontSize: Fonts.base,
fontSize: Number(Fonts.base),
lineHeight: 24,
fontWeight: '600',
},
@ -74,4 +108,16 @@ const styles = StyleSheet.create({
color: '#0a7ea4',
textDecorationLine: 'underline',
},
sfPro: {
fontSize: Number(Fonts.base),
lineHeight: 24,
fontWeight: '600',
fontFamily: Fonts.sfPro,
},
inter: {
fontSize: Number(Fonts.base),
lineHeight: 24,
fontWeight: '600',
fontFamily: Fonts.inter,
},
});

View File

@ -1,9 +1,32 @@
import { Colors } from '@/constants/Colors';
import { FontColor, Fonts } from '@/constants/Fonts';
import { useThemeColor } from '@/hooks/useThemeColor';
import { View, type ViewProps } from 'react-native';
import { ColorValue, isFontColorKey, ThemeColor } from './ThemedText';
type ThemedViewProps = ViewProps & {
className?: string;
bgColor?: FontColor | ColorValue | string;
};
export function ThemedView({ className, style, ...props }: ThemedViewProps) {
return <View className={className} style={style} {...props} />;
export function ThemedView({ className, style, bgColor, ...props }: ThemedViewProps) {
const themeColor = useThemeColor({ light: bgColor, dark: bgColor }, 'background');
const bgColorValue = (() => {
if (!bgColor) return themeColor;
// 检查是否是主题颜色
const themeColors = Object.keys(Colors.light) as ThemeColor[];
if (themeColors.includes(bgColor as ThemeColor)) {
return useThemeColor({ light: bgColor, dark: bgColor }, bgColor as ThemeColor);
}
// 检查是否是 Fonts 中定义的颜色
if (isFontColorKey(bgColor)) {
return Fonts[bgColor];
}
// 返回自定义颜色值
return bgColor;
})();
return <View className={className} style={[{ backgroundColor: bgColorValue }, style]} {...props} />;
}

View File

@ -1,9 +1,12 @@
import { Fonts } from "@/constants/Fonts";
import { fetchApi } from "@/lib/server-api-util";
import { User } from "@/types/user";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { ActivityIndicator, StyleSheet, TextInput, TouchableOpacity, View } from "react-native";
import { StyleSheet, TouchableOpacity, View } from "react-native";
import { ThemedText } from "../ThemedText";
import Button from "./ui/Button";
import TextInput from "./ui/TextInput";
interface LoginProps {
setIsSignUp?: (isSignUp: string) => void;
@ -69,45 +72,29 @@ const ForgetPwd = ({ setIsSignUp, updateUrlParam, setError }: LoginProps) => {
return (
<View style={styles.container}>
<View style={styles.inputContainer}>
<ThemedText style={styles.inputLabel}>
{t('auth.forgetPwd.title', { ns: 'login' })}
</ThemedText>
<TextInput
style={styles.textInput}
placeholder={t('auth.forgetPwd.emailPlaceholder', { ns: 'login' })}
placeholderTextColor="#ccc"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
autoCapitalize="none"
/>
</View>
<TouchableOpacity
style={[
styles.submitButton,
(isDisabled || loading) && styles.disabledButton
]}
onPress={handleSubmit}
disabled={isDisabled || loading}
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<ThemedText style={styles.buttonText}>
{isDisabled
? `${t("auth.forgetPwd.sendEmailBtnDisabled", { ns: "login" })} (${countdown}s)`
: t("auth.forgetPwd.sendEmailBtn", { ns: "login" })}
</ThemedText>
)}
</TouchableOpacity>
{/* 邮箱 */}
<TextInput
label={t('auth.forgetPwd.title', { ns: 'login' })}
placeholder={t('auth.forgetPwd.emailPlaceholder', { ns: 'login' })}
onChangeText={setEmail}
autoCapitalize="none"
value={email}
/>
{/* 发送邮箱 */}
<Button
isLoading={isDisabled || loading}
handleLogin={handleSubmit}
text={isDisabled
? `${t("auth.forgetPwd.sendEmailBtnDisabled", { ns: "login" })} (${countdown}s)`
: t("auth.forgetPwd.sendEmailBtn", { ns: "login" })}
/>
{/* 返回登录 */}
<TouchableOpacity
style={styles.backButton}
onPress={handleBackToLogin}
>
<ThemedText style={styles.backButtonText}>
<ThemedText type='inter' color="bgSecondary" size="sm">
{t('auth.forgetPwd.goback', { ns: 'login' })}
</ThemedText>
</TouchableOpacity>
@ -123,16 +110,24 @@ const styles = StyleSheet.create({
marginBottom: 20,
},
inputLabel: {
fontSize: 16,
color: '#1F2937',
fontSize: Fonts['base'],
color: Fonts['textPrimary'],
fontWeight: Fonts['bold'],
fontFamily: Fonts['sfPro'],
marginBottom: 8,
marginLeft: 8,
},
textInput: {
borderRadius: 12,
padding: 12,
fontSize: 16,
backgroundColor: '#FFF8DE',
borderRadius: Fonts['xs'],
paddingHorizontal: Fonts['base'],
paddingVertical: Fonts['xs'],
fontSize: Fonts['sm'],
lineHeight: Fonts['base'],
textAlignVertical: 'center',
backgroundColor: Fonts['bgInput'],
color: Fonts['textSecondary'],
fontFamily: Fonts['inter'],
paddingRight: Fonts['5xl'],
},
submitButton: {
width: '100%',
@ -151,11 +146,7 @@ const styles = StyleSheet.create({
backButton: {
alignSelf: 'center',
marginTop: 24,
},
backButtonText: {
color: '#1F2937',
fontSize: 14,
},
}
});
export default ForgetPwd;

View File

@ -1,14 +1,15 @@
import { Ionicons } from "@expo/vector-icons";
import { Fonts } from "@/constants/Fonts";
import { router } from "expo-router";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { ActivityIndicator, StyleSheet, TextInput, TouchableOpacity, View } from "react-native";
import { StyleSheet, TouchableOpacity, View } from "react-native";
import { useAuth } from "../../contexts/auth-context";
import { fetchApi } from "../../lib/server-api-util";
import { User } from "../../types/user";
import { ThemedText } from "../ThemedText";
import Button from "./ui/Button";
import TextInput from "./ui/TextInput";
const REMEMBER_ACCOUNT_KEY = 'fairclip_remembered_account';
interface LoginProps {
updateUrlParam: (status: string, value: string) => void;
setError: (error: string) => void;
@ -22,7 +23,6 @@ const Login = ({ updateUrlParam, setError, setShowPassword, showPassword }: Logi
const [isLoading, setIsLoading] = useState(false);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [rememberMe, setRememberMe] = useState(false);
const handleLogin = async () => {
if (!email) {
@ -69,85 +69,61 @@ const Login = ({ updateUrlParam, setError, setShowPassword, showPassword }: Logi
return (
<View style={styles.container}>
<View style={[styles.inputContainer, { marginBottom: 20 }]}>
<ThemedText style={styles.inputLabel}>
{t('auth.login.email', { ns: 'login' })}
</ThemedText>
<TextInput
style={styles.textInput}
placeholder={t('auth.login.accountPlaceholder', { ns: 'login' })}
placeholderTextColor="#ccc"
value={email}
onChangeText={(text) => {
setEmail(text);
setError('123');
}}
autoCapitalize="none"
/>
</View>
<View style={styles.inputContainer}>
<ThemedText style={styles.inputLabel}>
{t('auth.login.password', { ns: 'login' })}
</ThemedText>
<View style={styles.passwordInputContainer}>
<TextInput
style={[styles.textInput, { paddingRight: 48 }]}
placeholder={t('auth.login.passwordPlaceholder', { ns: 'login' })}
placeholderTextColor="#ccc"
value={password}
onChangeText={(text) => {
setPassword(text);
setError('123');
}}
secureTextEntry={!showPassword}
/>
<TouchableOpacity
style={styles.eyeIcon}
onPress={() => setShowPassword(!showPassword)}
>
<Ionicons
name={showPassword ? 'eye' : 'eye-off'}
size={20}
color="#666"
/>
</TouchableOpacity>
</View>
</View>
{/* 邮箱 */}
<TextInput
label={t('auth.login.email', { ns: 'login' })}
placeholder={t('auth.login.accountPlaceholder', { ns: 'login' })}
onChangeText={(text) => {
setEmail(text);
setError('123');
}}
autoCapitalize="none"
value={email}
/>
{/* 密码 */}
<TextInput
label={t('auth.login.password', { ns: 'login' })}
placeholder={t('auth.login.passwordPlaceholder', { ns: 'login' })}
onChangeText={(text) => {
setPassword(text);
setError('123');
}}
autoCapitalize="none"
value={password}
type="password"
setShowPassword={setShowPassword}
showPassword={showPassword}
containerStyle={{ marginBottom: 0 }}
/>
{/* 忘记密码 */}
<TouchableOpacity
style={styles.forgotPassword}
onPress={handleForgotPassword}
>
<ThemedText style={styles.forgotPasswordText}>
<ThemedText style={styles.forgotPasswordText} color="textPrimary" type="inter">
{t('auth.login.forgotPassword', { ns: 'login' })}
</ThemedText>
</TouchableOpacity>
<TouchableOpacity
style={[styles.loginButton, isLoading && { opacity: 0.7 }]}
onPress={handleLogin}
disabled={isLoading}
>
{isLoading ? (
<ActivityIndicator color="#fff" />
) : (
<ThemedText style={styles.loginButtonText}>
{t('auth.login.loginButton', { ns: 'login' })}
</ThemedText>
)}
</TouchableOpacity>
{/* 登录按钮 */}
<Button isLoading={isLoading} handleLogin={handleLogin} text={t('auth.login.loginButton', { ns: 'login' })} />
{/* 注册 */}
<View style={styles.signupContainer}>
<ThemedText style={styles.signupText}>
<ThemedText style={styles.signupText} type="sfPro">
{t('auth.login.signUpMessage', { ns: 'login' })}
</ThemedText>
<TouchableOpacity onPress={handleSignUp}>
<ThemedText style={styles.signupLink}>
<ThemedText style={styles.signupLink} type="sfPro">
{t('auth.login.signUp', { ns: 'login' })}
</ThemedText>
</TouchableOpacity>
</View>
{/* 第三方登录 */}
<View style={{ width: "100%", alignItems: "center", opacity: 0 }}>
<View style={styles.loginTypeContainer}>
<ThemedText>
@ -178,8 +154,8 @@ const styles = StyleSheet.create({
},
loginType: {
borderRadius: 12,
width: 54,
height: 54,
width: 42,
height: 42,
textAlign: 'center',
backgroundColor: '#FADBA1'
},
@ -187,19 +163,24 @@ const styles = StyleSheet.create({
marginBottom: 20,
},
inputLabel: {
fontSize: 16,
color: '#AC7E35',
fontWeight: '600',
fontSize: Fonts['base'],
color: Fonts['textPrimary'],
fontWeight: Fonts['bold'],
fontFamily: Fonts['sfPro'],
marginBottom: 8,
marginLeft: 8,
},
textInput: {
borderRadius: 12,
paddingHorizontal: 16,
paddingVertical: 12,
fontSize: 14,
borderRadius: Fonts['xs'],
paddingHorizontal: Fonts['base'],
paddingVertical: Fonts['xs'],
fontSize: Fonts['sm'],
lineHeight: Fonts['base'],
textAlignVertical: 'center',
backgroundColor: '#FFF8DE'
backgroundColor: Fonts['bgInput'],
color: Fonts['textSecondary'],
fontFamily: Fonts['inter'],
paddingRight: Fonts['5xl'],
},
passwordInputContainer: {
position: 'relative',
@ -237,11 +218,11 @@ const styles = StyleSheet.create({
},
signupText: {
color: '#AC7E35',
fontSize: 17,
fontSize: Fonts['sm'],
},
signupLink: {
color: '#E2793F',
fontSize: 17,
fontSize: Fonts['sm'],
fontWeight: '600',
marginLeft: 4,
textDecorationLine: 'underline',

View File

@ -0,0 +1,37 @@
import { ThemedText } from "@/components/ThemedText";
import { ActivityIndicator, StyleSheet, TouchableOpacity, ViewStyle } from "react-native";
interface ButtonProps {
isLoading?: boolean;
handleLogin?: () => void;
text: string;
containerStyle?: ViewStyle;
}
const Button = ({ isLoading, handleLogin, text, containerStyle }: ButtonProps) => {
return <TouchableOpacity
style={[styles.loginButton, isLoading && { opacity: 0.7 }, containerStyle]}
onPress={handleLogin}
disabled={isLoading}
>
{isLoading ? (
<ActivityIndicator color="#fff" />
) : (
<ThemedText type="sfPro" size="lg" weight="bold" color="textWhite">
{text}
</ThemedText>
)}
</TouchableOpacity>
}
const styles = StyleSheet.create({
loginButton: {
width: '100%',
backgroundColor: '#E2793F',
borderRadius: 28,
padding: 16,
alignItems: 'center',
marginBottom: 24,
},
})
export default Button

View File

@ -0,0 +1,111 @@
import { ThemedText } from "@/components/ThemedText";
import { Fonts } from "@/constants/Fonts";
import { Ionicons } from "@expo/vector-icons";
import { TextInput as RNTextInput, TextInputProps as RNTextInputProps, StyleProp, StyleSheet, TextStyle, TouchableOpacity, View, ViewStyle } from "react-native";
interface CustomTextInputProps {
label: string;
placeholder: string;
value: string;
onChangeText: (text: string) => void;
showPassword?: boolean;
setShowPassword?: (showPassword: boolean) => void;
setError?: (error: string) => void;
type?: 'default' | 'password';
containerStyle?: StyleProp<ViewStyle>;
style?: StyleProp<TextStyle>;
}
type TextInputProps = RNTextInputProps & CustomTextInputProps;
const TextInput = ({
type = 'default',
label,
placeholder,
value,
onChangeText,
setError,
showPassword,
setShowPassword,
style,
containerStyle,
...props
}: TextInputProps) => {
return (
<View style={[styles.inputContainer, containerStyle]}>
<ThemedText style={styles.inputLabel}>
{label}
</ThemedText>
<View style={styles.inputTextContainer}>
<RNTextInput
style={[styles.textInput, style]}
placeholder={placeholder}
placeholderTextColor={Fonts['placeholderTextColor']}
value={value}
onChangeText={onChangeText}
secureTextEntry={type === 'password' ? !showPassword : undefined}
{...props}
/>
{
type === 'password' &&
<TouchableOpacity
style={styles.eyeIcon}
onPress={() => {
if (setShowPassword) {
setShowPassword(!showPassword);
}
}}
>
<Ionicons
name={showPassword ? 'eye' : 'eye-off'}
size={20}
color="#666"
/>
</TouchableOpacity>
}
</View>
</View>
);
}
const styles = StyleSheet.create({
inputContainer: {
marginBottom: 20,
},
inputLabel: {
fontSize: Fonts['sm'],
color: Fonts['textPrimary'],
fontWeight: Fonts['bold'],
fontFamily: Fonts['sfPro'],
marginBottom: 8,
marginLeft: 8,
},
textInput: {
borderRadius: Fonts['xs'],
paddingHorizontal: Fonts['base'],
paddingVertical: Fonts['sm'],
fontSize: Fonts['sm'],
lineHeight: Fonts['base'],
textAlignVertical: 'center',
backgroundColor: Fonts['bgInput'],
color: Fonts['textSecondary'],
fontFamily: Fonts['inter'],
paddingRight: 48, // Make space for the eye icon
},
inputTextContainer: {
position: 'relative',
},
eyeIcon: {
position: 'absolute',
right: 12,
top: '50%',
transform: [{ translateY: -10 }], // Half of the icon's height (20/2 = 10)
height: 20,
width: 20,
justifyContent: 'center',
alignItems: 'center',
},
})
export default TextInput;

View File

@ -1,14 +1,18 @@
export const Fonts = {
// Font family
primary: 'english',
quicksand: 'quicksand',
sfPro: 'sfPro',
inter: 'inter',
// Font weights
regular: '400',
medium: '500',
semiBold: '600',
bold: '700',
extraBold: '800',
// Font sizes
xxs: 11,
xs: 12,
sm: 14,
base: 16,
@ -18,7 +22,20 @@ export const Fonts = {
'3xl': 30,
'4xl': 36,
'5xl': 48,
// color
bgPrimary: '#FFB645',
bgSecondary: '#E2793F',
bgInput: '#FFF8DE',
textPrimary: '#AC7E35',
textSecondary: '#4C320C',
textThird: '#7F786F',
textWhite: "#FFFFFF",
placeholderTextColor: "#ccc",
} as const;
export type FontWeight = keyof Omit<typeof Fonts, 'primary' | 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl'>;
export type FontWeight = keyof Omit<typeof Fonts, 'quicksand' | 'sfPro' | 'inter' | 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl'>;
export type FontSize = 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl';
export type FontColor = 'bgPrimary' | 'bgSecondary' | 'textPrimary' | 'textSecondary' | 'textThird' | 'textWhite';