admin管理员组文章数量:1377604
I'm currently trying to build a new app to journal things. I have a new entry file that I've attached. When I'm on a textfield I want to have above the keyboard an up and down navigation chevron and a done button that closes the keyboard.
This works perfectly on the first textfield marked 'title' and when I press the down chevron it navigates fine to the next box, but then there are not chevrons or done button, and there aren't for any of the other boxes. But if I click back on the first one they're there again.
I have tried several things surrounding the onblur, activeInputField, and several other things. I have made it so that the chevrons don't work on the first and last textfield, so there might be something with that, but I don't think so to be honest.
import { useState, useRef, useEffect } from 'react';
import {
View,
Text,
TextInput,
ScrollView,
StyleSheet,
TouchableOpacity,
KeyboardAvoidingView,
Platform,
TouchableWithoutFeedback,
Keyboard,
Animated,
InputAccessoryView,
Dimensions,
LayoutAnimation,
UIManager,
ViewStyle,
TextStyle
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { router } from 'expo-router';
import { ChevronUp, ChevronDown } from 'lucide-react-native';
import * as Haptics from 'expo-haptics';
import Ionicons from '@expo/vector-icons/Ionicons';
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
const JOURNAL_INPUT_ACCESSORY_ID = 'journalInputAccessoryID';
const { width: SCREEN_WIDTH } = Dimensions.get('window');
interface Styles extends StyleSheet.NamedStyles<{
container: ViewStyle;
content: ViewStyle;
heading: TextStyle;
input: TextStyle;
textArea: TextStyle;
inputFocused: TextStyle;
label: TextStyle;
charCount: ViewStyle;
charCountText: TextStyle;
inputAccessoryContainer: ViewStyle;
inputAccessoryContent: ViewStyle;
navButton: ViewStyle;
navButtonDisabled: ViewStyle;
doneButton: ViewStyle;
doneButtonText: TextStyle;
}> {}
export default function JournalEntryScreen() {
const [journalTitle, setJournalTitle] = useState('');
const [journalSituation, setJournalSituation] = useState('');
const [journalImmediateReaction, setJournalImmediateReaction] = useState('');
const [journalBetterResponse, setJournalBetterResponse] = useState('');
const [journalFollowUp, setJournalFollowUp] = useState('');
const [activeInputField, setActiveInputField] = useState('');
const [keyboardSpacing, setKeyboardSpacing] = useState(0);
const fadeAnim = useRef(new Animated.Value(0)).current;
const slideAnim = useRef(new Animated.Value(0)).current;
const journalTitleRef = useRef<TextInput>(null);
const journalSituationRef = useRef<TextInput>(null);
const journalReactionRef = useRef<TextInput>(null);
const journalResponseRef = useRef<TextInput>(null);
const journalFollowUpRef = useRef<TextInput>(null);
const journalScrollViewRef = useRef<ScrollView>(null);
const handleJournalSave = () => {
router.push('/journal');
};
useEffect(() => {
const keyboardWillShow = Keyboard.addListener(
Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow',
e => setKeyboardSpacing(e.endCoordinates.height)
);
const keyboardWillHide = Keyboard.addListener(
Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide',
() => setKeyboardSpacing(0)
);
return () => {
keyboardWillShow.remove();
keyboardWillHide.remove();
};
}, []);
useEffect(() => {
if (activeInputField) {
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 1,
duration: 200,
useNativeDriver: true,
}),
Animated.spring(slideAnim, {
toValue: 1,
tension: 20,
friction: 7,
useNativeDriver: true,
}),
]).start();
} else {
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 0,
duration: 150,
useNativeDriver: true,
}),
Animated.spring(slideAnim, {
toValue: 0,
tension: 20,
friction: 7,
useNativeDriver: true,
}),
]).start();
}
}, [activeInputField]);
const handleJournalFieldFocus = (fieldName: string, scrollPosition: number) => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setActiveInputField(fieldName);
Haptics.selectionAsync();
setTimeout(() => {
journalScrollViewRef.current?.scrollTo({
y: scrollPosition,
animated: true
});
}, 100);
};
const handleJournalFieldBlur = () => {
requestAnimationFrame(() => {
if (!Keyboard.isVisible()) {
setActiveInputField('');
}
});
};
const navigateToNextJournalField = (currentField: string) => {
Haptics.selectionAsync();
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
switch (currentField) {
case 'title':
journalSituationRef.current?.focus();
setActiveInputField('situation');
break;
case 'situation':
journalReactionRef.current?.focus();
setActiveInputField('immediateReaction');
break;
case 'immediateReaction':
journalResponseRef.current?.focus();
setActiveInputField('betterResponse');
break;
case 'betterResponse':
journalFollowUpRef.current?.focus();
setActiveInputField('followUp');
break;
case 'followUp':
Keyboard.dismiss();
setActiveInputField('');
break;
}
};
const navigateToPreviousJournalField = (currentField: string) => {
Haptics.selectionAsync();
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
switch (currentField) {
case 'followUp':
journalResponseRef.current?.focus();
setActiveInputField('betterResponse');
break;
case 'betterResponse':
journalReactionRef.current?.focus();
setActiveInputField('immediateReaction');
break;
case 'immediateReaction':
journalSituationRef.current?.focus();
setActiveInputField('situation');
break;
case 'situation':
journalTitleRef.current?.focus();
setActiveInputField('title');
break;
}
};
const handleJournalKeyPress = (fieldName: string, event: any) => {
if (event.nativeEvent.key === 'Enter' && !event.nativeEvent.shiftKey) {
event.preventDefault?.();
navigateToNextJournalField(fieldName);
return true;
}
return false;
};
const renderJournalInputAccessory = () => {
if (Platform.OS !== 'ios') return null;
const isFirstField = activeInputField === 'title';
const isLastField = activeInputField === 'followUp';
return (
<InputAccessoryView nativeID={JOURNAL_INPUT_ACCESSORY_ID}>
<View style={styles.inputAccessoryContainer}>
<View style={styles.inputAccessoryContent}>
<TouchableOpacity
onPress={() => navigateToPreviousJournalField(activeInputField)}
disabled={isFirstField}
style={[styles.navButton, isFirstField && styles.navButtonDisabled]}
>
<Ionicons
name="chevron-up"
size={24}
color={isFirstField ? '#94a3b8' : '#0284c7'}
/>
</TouchableOpacity>
<TouchableOpacity
onPress={() => navigateToNextJournalField(activeInputField)}
disabled={isLastField}
style={[styles.navButton, isLastField && styles.navButtonDisabled]}
>
<Ionicons
name="chevron-down"
size={24}
color={isLastField ? '#94a3b8' : '#0284c7'}
/>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
Keyboard.dismiss();
setActiveInputField('');
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
}}
style={styles.doneButton}
>
<Text style={styles.doneButtonText}>Done</Text>
</TouchableOpacity>
</View>
</View>
</InputAccessoryView>
);
};
const renderJournalCharCount = (text: string, maxLength: number = 500) => {
const percentage = (text.length / maxLength) * 100;
const color = percentage > 90 ? '#ef4444' : percentage > 75 ? '#f59e0b' : '#94a3b8';
return (
<Text style={[styles.charCountText, { color }]}>
{text.length}/{maxLength}
</Text>
);
};
return (
<SafeAreaView style={styles.container}>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.container}
keyboardVerticalOffset={Platform.OS === 'ios' ? 64 : 0}
>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<ScrollView
ref={journalScrollViewRef}
contentContainerStyle={styles.content}
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={true}
scrollIndicatorInsets={{ right: 1 }}
>
<Text style={styles.heading}>New Journal Entry</Text>
<Animated.View style={[
styles.inputGroup,
{
transform: [{
scale: activeInputField === 'title' ? 1.02 : 1,
}],
}
]}>
<Text style={styles.label}>Title</Text>
<TextInput
ref={journalTitleRef}
style={[
styles.input,
activeInputField === 'title' && styles.inputFocused
]}
value={journalTitle}
onChangeText={setJournalTitle}
placeholder="Give your entry a title..."
placeholderTextColor="#94a3b8"
returnKeyType="next"
onFocus={() => handleJournalFieldFocus('title', 0)}
onBlur={handleJournalFieldBlur}
onSubmitEditing={() => navigateToNextJournalField('title')}
blurOnSubmit={false}
inputAccessoryViewID={JOURNAL_INPUT_ACCESSORY_ID}
maxLength={100}
/>
{renderJournalCharCount(journalTitle, 100)}
</Animated.View>
<Animated.View style={[
styles.inputGroup,
{
transform: [{
scale: activeInputField === 'situation' ? 1.02 : 1,
}],
}
]}>
<Text style={styles.label}>Situation & Feelings</Text>
<TextInput
ref={journalSituationRef}
style={[
styles.input,
styles.textArea,
activeInputField === 'situation' && styles.inputFocused
]}
value={journalSituation}
onChangeText={setJournalSituation}
placeholder="Describe what happened and how you're feeling..."
placeholderTextColor="#94a3b8"
multiline={true}
textAlignVertical="top"
returnKeyType="next"
blurOnSubmit={false}
onFocus={() => handleJournalFieldFocus('situation', 150)}
onBlur={handleJournalFieldBlur}
onSubmitEditing={() => navigateToNextJournalField('situation')}
onKeyPress={(e) => handleJournalKeyPress('situation', e)}
inputAccessoryViewID={JOURNAL_INPUT_ACCESSORY_ID}
maxLength={500}
enablesReturnKeyAutomatically={true}
/>
{renderJournalCharCount(journalSituation)}
</Animated.View>
<Animated.View style={[
styles.inputGroup,
{
transform: [{
scale: activeInputField === 'immediateReaction' ? 1.02 : 1,
}],
}
]}>
<Text style={styles.label}>Immediate Reaction</Text>
<TextInput
ref={journalReactionRef}
style={[
styles.input,
styles.textArea,
activeInputField === 'immediateReaction' && styles.inputFocused
]}
value={journalImmediateReaction}
onChangeText={setJournalImmediateReaction}
placeholder="What's your impulse? How do you feel like reacting?"
placeholderTextColor="#94a3b8"
multiline={true}
textAlignVertical="top"
returnKeyType="next"
blurOnSubmit={false}
onFocus={() => handleJournalFieldFocus('immediateReaction', 300)}
onBlur={handleJournalFieldBlur}
onSubmitEditing={() => navigateToNextJournalField('immediateReaction')}
onKeyPress={(e) => handleJournalKeyPress('immediateReaction', e)}
inputAccessoryViewID={JOURNAL_INPUT_ACCESSORY_ID}
maxLength={500}
enablesReturnKeyAutomatically={true}
/>
{renderJournalCharCount(journalImmediateReaction)}
</Animated.View>
<Animated.View style={[
styles.inputGroup,
{
transform: [{
scale: activeInputField === 'betterResponse' ? 1.02 : 1,
}],
}
]}>
<Text style={styles.label}>Better Response</Text>
<TextInput
ref={journalResponseRef}
style={[
styles.input,
styles.textArea,
activeInputField === 'betterResponse' && styles.inputFocused
]}
value={journalBetterResponse}
onChangeText={setJournalBetterResponse}
placeholder="What would be a better way to handle this?"
placeholderTextColor="#94a3b8"
multiline={true}
textAlignVertical="top"
returnKeyType="next"
blurOnSubmit={false}
onFocus={() => handleJournalFieldFocus('betterResponse', 450)}
onBlur={handleJournalFieldBlur}
onSubmitEditing={() => navigateToNextJournalField('betterResponse')}
onKeyPress={(e) => handleJournalKeyPress('betterResponse', e)}
inputAccessoryViewID={JOURNAL_INPUT_ACCESSORY_ID}
maxLength={500}
enablesReturnKeyAutomatically={true}
/>
{renderJournalCharCount(journalBetterResponse)}
</Animated.View>
<Animated.View style={[
styles.inputGroup,
{
transform: [{
scale: activeInputField === 'followUp' ? 1.02 : 1,
}],
}
]}>
<Text style={styles.label}>Follow-up Reflection (24-72 hours later)</Text>
<TextInput
ref={journalFollowUpRef}
style={[
styles.input,
styles.textArea,
activeInputField === 'followUp' && styles.inputFocused
]}
value={journalFollowUp}
onChangeText={setJournalFollowUp}
placeholder="Did your better response improve the outcome? What did you learn?"
placeholderTextColor="#94a3b8"
multiline={true}
textAlignVertical="top"
returnKeyType="done"
blurOnSubmit={true}
onFocus={() => handleJournalFieldFocus('followUp', 750)}
onBlur={handleJournalFieldBlur}
onSubmitEditing={() => Keyboard.dismiss()}
onKeyPress={(e) => handleJournalKeyPress('followUp', e)}
inputAccessoryViewID={JOURNAL_INPUT_ACCESSORY_ID}
maxLength={500}
enablesReturnKeyAutomatically={true}
/>
{renderJournalCharCount(journalFollowUp)}
</Animated.View>
<TouchableOpacity
style={styles.button}
onPress={() => {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
handleJournalSave();
}}
activeOpacity={0.8}
>
<Text style={styles.buttonText}>Save Entry</Text>
</TouchableOpacity>
<View style={[styles.keyboardSpacer, { height: keyboardSpacing > 0 ? keyboardSpacing : 60 }]} />
</ScrollView>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
{renderJournalInputAccessory()}
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
} as ViewStyle,
content: {
padding: 16,
paddingBottom: 32,
} as ViewStyle,
heading: {
fontFamily: 'Inter_600SemiBold',
fontSize: 24,
color: '#0f172a',
marginBottom: 24,
} as TextStyle,
inputGroup: {
marginBottom: 20,
borderRadius: 16,
backgroundColor: '#fff',
padding: 16,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.05,
shadowRadius: 3.84,
elevation: 2,
} as ViewStyle,
label: {
fontFamily: 'Inter_500Medium',
fontSize: 14,
color: '#64748b',
marginBottom: 4,
} as TextStyle,
input: {
fontFamily: 'Inter_400Regular',
fontSize: 16,
color: '#0f172a',
borderWidth: 1,
borderColor: '#e2e8f0',
borderRadius: 8,
padding: 12,
backgroundColor: '#f8fafc',
} as TextStyle,
inputFocused: {
borderColor: '#0284c7',
backgroundColor: '#fff',
shadowColor: '#0284c7',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.1,
shadowRadius: 4,
} as TextStyle,
textArea: {
minHeight: 120,
maxHeight: 200,
textAlignVertical: 'top',
} as TextStyle,
button: {
backgroundColor: '#6366f1',
padding: 16,
borderRadius: 12,
alignItems: 'center',
marginTop: 12,
shadowColor: '#6366f1',
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.2,
shadowRadius: 8,
elevation: 4,
} as ViewStyle,
buttonText: {
color: '#fff',
fontSize: 16,
fontFamily: 'Inter_600SemiBold',
} as TextStyle,
keyboardSpacer: {
height: 60,
} as ViewStyle,
inputAccessoryContainer: {
backgroundColor: '#f8fafc',
borderTopWidth: 1,
borderTopColor: '#e2e8f0',
padding: 8,
} as ViewStyle,
inputAccessoryContent: {
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center',
paddingHorizontal: 8,
} as ViewStyle,
navButton: {
padding: 8,
marginHorizontal: 4,
} as ViewStyle,
navButtonDisabled: {
opacity: 0.5,
} as ViewStyle,
doneButton: {
marginLeft: 12,
paddingVertical: 8,
paddingHorizontal: 16,
borderRadius: 8,
} as ViewStyle,
doneButtonText: {
color: '#0284c7',
fontSize: 16,
fontWeight: '600',
} as TextStyle,
charCount: {
alignSelf: 'flex-end',
marginTop: 4,
} as ViewStyle,
charCountText: {
fontFamily: 'Inter_400Regular',
fontSize: 12,
color: '#94a3b8',
} as TextStyle,
});
本文标签:
版权声明:本文标题:ios - Why do my navigation chevrons and done button appear above one text field, but then none of the others? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744489497a2608649.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论