mobile: let users dismiss the keyboard from a task card
Focusing a task field opened the keyboard with no way out — it covered the card and the stream's tap-to-advance zones. Now: - A "Done" pill appears at the card's top-right while editing (reusing the existing `editing` flag) and calls Keyboard.dismiss(); the title row reserves space so the pill never overlaps a long title. - The card ScrollView gains keyboardDismissMode (interactive on iOS, on-drag on Android) so dragging the card also dismisses the keyboard. Dismissing blurs the active field, which flips `editing` off and resumes the dwell timer and tap navigation automatically. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01LqGPzXQ1AA9CqYHgCgmbtV
This commit is contained in:
@@ -1,5 +1,13 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { Pressable, ScrollView, Text, TextInput, View } from 'react-native';
|
import {
|
||||||
|
Keyboard,
|
||||||
|
Platform,
|
||||||
|
Pressable,
|
||||||
|
ScrollView,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
View,
|
||||||
|
} from 'react-native';
|
||||||
import { deleteField } from 'firebase/firestore';
|
import { deleteField } from 'firebase/firestore';
|
||||||
import { Check, Plus, X } from 'lucide-react-native';
|
import { Check, Plus, X } from 'lucide-react-native';
|
||||||
import type { ChecklistItem, Particle } from '@/api/types';
|
import type { ChecklistItem, Particle } from '@/api/types';
|
||||||
@@ -158,15 +166,23 @@ export function TaskParticleView({
|
|||||||
className="flex-1 items-center justify-center px-6"
|
className="flex-1 items-center justify-center px-6"
|
||||||
style={{ paddingTop: safe.top + 16, paddingBottom: safe.bottom + 16 }}
|
style={{ paddingTop: safe.top + 16, paddingBottom: safe.bottom + 16 }}
|
||||||
>
|
>
|
||||||
|
<View className="w-full max-w-xl" style={{ maxHeight: '100%' }}>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
className="max-h-full w-full max-w-xl rounded-2xl bg-white/10"
|
className="max-h-full rounded-2xl bg-white/10"
|
||||||
contentContainerClassName="px-5 py-5 gap-5"
|
contentContainerClassName="px-5 py-5 gap-5"
|
||||||
showsVerticalScrollIndicator
|
showsVerticalScrollIndicator
|
||||||
indicatorStyle="white"
|
indicatorStyle="white"
|
||||||
keyboardShouldPersistTaps="handled"
|
keyboardShouldPersistTaps="handled"
|
||||||
|
// Dragging the card dismisses the keyboard (interactive follow on
|
||||||
|
// iOS; on-drag on Android, which lacks the interactive variant).
|
||||||
|
keyboardDismissMode={
|
||||||
|
Platform.OS === 'ios' ? 'interactive' : 'on-drag'
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{/* Title + done */}
|
{/* Title + done */}
|
||||||
<View className="flex-row items-start gap-3">
|
<View
|
||||||
|
className={cn('flex-row items-start gap-3', editing && 'pr-12')}
|
||||||
|
>
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={handleToggleDone}
|
onPress={handleToggleDone}
|
||||||
hitSlop={8}
|
hitSlop={8}
|
||||||
@@ -210,7 +226,9 @@ export function TaskParticleView({
|
|||||||
onEndEditing={(e) => {
|
onEndEditing={(e) => {
|
||||||
const value = e.nativeEvent.text;
|
const value = e.nativeEvent.text;
|
||||||
if (value !== (notes ?? '')) {
|
if (value !== (notes ?? '')) {
|
||||||
void updateParticleProperties<'task'>(docPath, { notes: value });
|
void updateParticleProperties<'task'>(docPath, {
|
||||||
|
notes: value,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
placeholder="Add notes…"
|
placeholder="Add notes…"
|
||||||
@@ -271,6 +289,23 @@ export function TaskParticleView({
|
|||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
{/* While a field is focused the keyboard hides the stream's tap-zones,
|
||||||
|
so offer an explicit way out. Dismissing blurs the active field,
|
||||||
|
which flips `editing` off and resumes the dwell + tap navigation. */}
|
||||||
|
{editing ? (
|
||||||
|
<View className="absolute right-2 top-2">
|
||||||
|
<Pressable
|
||||||
|
onPress={() => Keyboard.dismiss()}
|
||||||
|
hitSlop={8}
|
||||||
|
accessibilityLabel="Done editing"
|
||||||
|
className="rounded-full bg-white/15 px-3 py-1.5"
|
||||||
|
>
|
||||||
|
<Text className="text-white text-sm font-medium">Done</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user