feat: add calendar date picker, nav icons, and input-focused keyboard scrolling
All checks were successful
Luggage List Build / build-web (push) Successful in 28s
Luggage List Build / build-android (push) Successful in 5m59s
Luggage List Build / release (push) Successful in 11s

This commit is contained in:
2026-04-18 13:12:51 +02:00
parent 23a5f0ebae
commit 30ee53fe75
7 changed files with 260 additions and 47 deletions

View File

@@ -5,6 +5,7 @@ import * as ImagePicker from 'expo-image-picker';
import { StatusBar } from 'expo-status-bar';
import BottomTab from './components/BottomTab';
import TripPicker from './components/TripPicker';
import DatePickerModal from './components/DatePickerModal';
import ItemModal from './modals/ItemModal';
import CheckupFixModal from './modals/CheckupFixModal';
import TripsTab from './tabs/TripsTab';
@@ -45,6 +46,7 @@ export default function AppRoot() {
const [selectedTripId, setSelectedTripId] = useState(null);
const [tripForm, setTripForm] = useState(emptyTripForm());
const [datePicker, setDatePicker] = useState({ visible: false, field: 'startDate' });
const [itemModalVisible, setItemModalVisible] = useState(false);
const [itemForm, setItemForm] = useState(emptyItemForm());
@@ -130,6 +132,15 @@ export default function AppRoot() {
setItemForm((prev) => ({ ...prev, [field]: value }));
}
function openDatePicker(field) {
setDatePicker({ visible: true, field });
}
function onSelectDate(ymd) {
updateTripForm(datePicker.field, ymd);
setDatePicker((prev) => ({ ...prev, visible: false }));
}
async function pickImage(onPicked) {
const perm = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (!perm.granted) {
@@ -158,7 +169,7 @@ export default function AppRoot() {
const end = parseYMD(tripForm.endDate);
if (!start || !end) {
Alert.alert('Invalid dates', 'Use YYYY-MM-DD format.');
Alert.alert('Invalid dates', 'Please select valid trip dates.');
return;
}
@@ -456,16 +467,21 @@ export default function AppRoot() {
createFreshCheckupSession();
}
function focusToEnd() {
function onInputFocus(event) {
const target = event?.target;
if (!target) return;
setTimeout(() => {
scrollRef.current?.scrollToEnd?.({ animated: true });
const scrollFn = scrollRef.current?.scrollResponderScrollNativeHandleToKeyboard;
if (typeof scrollFn === 'function') {
scrollFn(target, 90, true);
}
}, 80);
}
if (!loaded) {
return (
<SafeAreaView style={styles.safe}>
<StatusBar style="light" />
<StatusBar style="light" translucent={false} />
<View style={styles.center}>
<Text style={styles.muted}>Loading local data...</Text>
</View>
@@ -475,7 +491,7 @@ export default function AppRoot() {
return (
<SafeAreaView style={styles.safe}>
<StatusBar style="light" />
<StatusBar style="light" translucent={false} />
<KeyboardAvoidingView style={styles.flex} behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
<ScrollView
@@ -484,6 +500,7 @@ export default function AppRoot() {
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}
>
<View style={styles.statusSpacer} />
<TripPicker trips={data.trips} selectedTripId={selectedTripId} onChooseTrip={setSelectedTripId} />
{tab === 'trips' && (
@@ -498,8 +515,9 @@ export default function AppRoot() {
chooseTrip={setSelectedTripId}
setTripAsTemplate={setTripAsTemplate}
deleteTrip={deleteTrip}
focusToEnd={focusToEnd}
onInputFocus={onInputFocus}
defaultTemplateTripId={data.defaultTemplateTripId}
openDatePicker={openDatePicker}
/>
)}
@@ -525,6 +543,7 @@ export default function AppRoot() {
{tab === 'history' && (
<HistoryTab
selectedTrip={selectedTrip}
selectedTripCheckups={selectedTripCheckups}
selectedCheckupId={selectedCheckupId}
setSelectedCheckupId={setSelectedCheckupId}
@@ -535,6 +554,14 @@ export default function AppRoot() {
<BottomTab current={tab} onChange={setTab} />
<DatePickerModal
visible={datePicker.visible}
title={datePicker.field === 'startDate' ? 'Pick start date' : 'Pick end date'}
value={tripForm[datePicker.field]}
onClose={() => setDatePicker((prev) => ({ ...prev, visible: false }))}
onSelect={onSelectDate}
/>
<ItemModal
visible={itemModalVisible}
itemForm={itemForm}