Files
mindfulness/client/components/onboarding/NameInputStep.tsx
2026-02-05 01:49:29 +08:00

138 lines
4.1 KiB
TypeScript

import React, { useEffect, useRef, useState } from 'react';
import { View, StyleSheet, TextInput, Platform, Animated, TouchableOpacity, Text } from 'react-native';
import { useTranslation } from 'react-i18next';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { OnboardingColors } from '@/constants/OnboardingTheme';
import BtnNotClicked from '@/assets/images/icon/btn_Notclicked.svg';
import BtnClicked from '@/assets/images/icon/btn_clicked.svg';
import EnterLightIcon from '@/assets/images/icon/enter_Light_icon.svg';
interface NameInputStepProps {
value: string;
onChangeText: (text: string) => void;
onNext: () => void;
}
export function NameInputStep({ value, onChangeText, onNext }: NameInputStepProps) {
const { t } = useTranslation();
const insets = useSafeAreaInsets();
const [isFocused, setIsFocused] = useState(false);
const blinkAnim = useRef(new Animated.Value(1)).current;
const hasInput = value.trim().length > 0;
useEffect(() => {
const animation = Animated.loop(
Animated.sequence([
Animated.timing(blinkAnim, { toValue: 0, duration: 500, useNativeDriver: true }),
Animated.timing(blinkAnim, { toValue: 1, duration: 500, useNativeDriver: true }),
])
);
if (isFocused) {
animation.start();
} else {
animation.stop();
blinkAnim.setValue(0);
}
return () => animation.stop();
}, [blinkAnim, isFocused]);
return (
<View style={styles.container}>
<View style={styles.inputCard}>
<View style={styles.inputWrapper}>
{/* 显示层:文案 + 跟随的光标 */}
<View style={styles.displayLayer}>
<Text
style={[
styles.displayText,
(!isFocused && !hasInput) && { color: OnboardingColors.textSecondary }
]}
>
{hasInput ? value : isFocused ? '' : t('onboardingSurvey.steps.name.placeholder')}
</Text>
{isFocused && (
<Animated.View style={[styles.cursorWrapper, { opacity: blinkAnim, marginLeft: 2 }]}>
<EnterLightIcon width={3} height={27} />
</Animated.View>
)}
</View>
{/* 交互层:隐藏的输入框 */}
<TextInput
style={styles.hiddenInput}
value={value}
onChangeText={onChangeText}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
caretHidden={true}
autoCorrect={false}
spellCheck={false}
/>
</View>
</View>
<View style={[styles.footer, { bottom: insets.bottom + 16 }]}>
<TouchableOpacity
onPress={onNext}
disabled={!hasInput}
activeOpacity={0.8}
>
{hasInput ? <BtnClicked width={87} height={57} /> : <BtnNotClicked width={87} height={57} />}
</TouchableOpacity>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
paddingTop: 20,
},
inputCard: {
width: 335,
height: 75,
backgroundColor: OnboardingColors.cardBackground,
borderRadius: 20,
justifyContent: 'center',
alignItems: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.05,
shadowRadius: 10,
elevation: 2,
},
inputWrapper: {
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
},
displayLayer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
displayText: {
fontSize: 22,
fontFamily: Platform.select({ ios: 'STIX Two Text', android: 'serif', default: 'serif' }),
fontWeight: '600',
color: OnboardingColors.textPrimary,
textAlign: 'center',
},
hiddenInput: {
...StyleSheet.absoluteFillObject,
color: 'transparent', // 文字透明,只负责输入逻辑
fontSize: 22,
textAlign: 'center',
},
cursorWrapper: {
// 默认居中显示时,光标在左侧
},
footer: {
position: 'absolute',
alignItems: 'center',
}
});