fix(native/mail): duplicate add-button in empty state + intro hints in ConnectMailSheet
- mail.tsx: hide section-header "+" button when accounts.length === 0 — MailEmptyState's CTA is the sole add trigger; also replaces Pressable with TouchableOpacity - MailEmptyState: Pressable → TouchableOpacity (no-Pressable rule) - SheetFieldStack: add optional `intro?: ReactNode` prop rendered in a flexShrink:1 ScrollView above chips/active-input so it compresses gracefully when the keyboard is up - ConnectMailSheet: move app-password guide + green AES block into `intro` prop so they're visible from the start, before the user types anything Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3eaf3f098a
commit
a3f892ddac
@ -5,6 +5,7 @@ import {
|
||||
Pressable,
|
||||
ScrollView,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { useBottomTabBarHeight } from 'react-native-bottom-tabs';
|
||||
@ -173,7 +174,8 @@ export default function MailScreen() {
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Section header with prominent + button */}
|
||||
{/* Section header with prominent + button — hidden in empty state (CTA lives there) */}
|
||||
{accounts.length > 0 && (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
@ -211,10 +213,11 @@ export default function MailScreen() {
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Pressable
|
||||
<TouchableOpacity
|
||||
onPress={handleAddPress}
|
||||
disabled={limitReached}
|
||||
android_ripple={{ color: '#0066cc' }}
|
||||
activeOpacity={limitReached ? 1 : 0.8}
|
||||
accessibilityLabel={t('mail.add_account_a11y')}
|
||||
style={{
|
||||
backgroundColor: limitReached ? colors.surfaceElevated : '#007AFF',
|
||||
borderRadius: 12,
|
||||
@ -224,11 +227,6 @@ export default function MailScreen() {
|
||||
shadowOpacity: limitReached ? 0 : 0.25,
|
||||
shadowRadius: 8,
|
||||
elevation: limitReached ? 0 : 4,
|
||||
}}
|
||||
accessibilityLabel={t('mail.add_account_a11y')}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 14,
|
||||
@ -250,9 +248,9 @@ export default function MailScreen() {
|
||||
>
|
||||
{t('mail.add_account')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Account cards or empty */}
|
||||
{accounts.length === 0 ? (
|
||||
|
||||
@ -27,6 +27,13 @@ export type SheetField = {
|
||||
|
||||
type Props = {
|
||||
fields: SheetField[];
|
||||
/**
|
||||
* Immer sichtbarer Bereich über Chips + aktivem Input — für Hinweise, die der User
|
||||
* sehen soll BEVOR er tippt. Wird in einer eigenen ScrollView mit `flexShrink:1`
|
||||
* gerendert, sodass er bei kleinem verfügbaren Platz (Tastatur offen) schrumpft,
|
||||
* der Eingabebereich aber nie weggedrückt wird.
|
||||
*/
|
||||
intro?: ReactNode;
|
||||
/** Rendert sich nach dem letzten Feld — sichtbar sobald alle Felder ausgefüllt sind. */
|
||||
children?: ReactNode;
|
||||
onComplete?: () => void;
|
||||
@ -41,7 +48,7 @@ type Props = {
|
||||
*
|
||||
* Wird als `children` von `<FormSheet>` benutzt.
|
||||
*/
|
||||
export function SheetFieldStack({ fields, children, onComplete }: Props) {
|
||||
export function SheetFieldStack({ fields, intro, children, onComplete }: Props) {
|
||||
const colors = useColors();
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
|
||||
@ -84,9 +91,23 @@ export function SheetFieldStack({ fields, children, onComplete }: Props) {
|
||||
const isLast = activeIndex === fields.length - 1;
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
{/* Intro: immer sichtbar, schrumpft bei wenig Platz (Tastatur offen) */}
|
||||
{intro != null && (
|
||||
<ScrollView
|
||||
style={{ flexShrink: 1 }}
|
||||
contentContainerStyle={{ padding: 16, paddingBottom: 0 }}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{intro}
|
||||
</ScrollView>
|
||||
)}
|
||||
|
||||
{/* Chips + aktives Feld + Post-Completion-Inhalt */}
|
||||
<ScrollView
|
||||
style={{ flex: 1 }}
|
||||
contentContainerStyle={{ padding: 16, gap: 10 }}
|
||||
contentContainerStyle={{ padding: 16, paddingTop: intro != null ? 10 : 16, gap: 10 }}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
@ -221,5 +242,6 @@ export function SheetFieldStack({ fields, children, onComplete }: Props) {
|
||||
{/* Rest des Formulars — sichtbar wenn alle Felder durch */}
|
||||
{allDone && children}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@ -189,9 +189,9 @@ export function ConnectMailSheet({ visible, onClose, onSuccess }: Props) {
|
||||
),
|
||||
},
|
||||
]}
|
||||
onComplete={() => setFieldsComplete(true)}
|
||||
>
|
||||
{/* App-Password-Guide — über den Datenschutz-Hinweis */}
|
||||
intro={
|
||||
<View style={{ gap: 10 }}>
|
||||
{/* App-Password-Guide — provider-spezifisch, nicht für 'other' */}
|
||||
{selectedProvider && selectedProvider.id !== 'other' && (
|
||||
<View
|
||||
style={{
|
||||
@ -202,7 +202,6 @@ export function ConnectMailSheet({ visible, onClose, onSuccess }: Props) {
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: '#bfdbfe',
|
||||
marginBottom: 10,
|
||||
}}
|
||||
>
|
||||
<Ionicons
|
||||
@ -248,7 +247,7 @@ export function ConnectMailSheet({ visible, onClose, onSuccess }: Props) {
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Datenschutz-Hinweis */}
|
||||
{/* Datenschutz-Zusicherung — immer sichtbar */}
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
@ -258,7 +257,6 @@ export function ConnectMailSheet({ visible, onClose, onSuccess }: Props) {
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: '#bbf7d0',
|
||||
marginBottom: 10,
|
||||
}}
|
||||
>
|
||||
<Ionicons name="shield-checkmark" size={16} color="#16a34a" style={{ marginTop: 1 }} />
|
||||
@ -274,7 +272,10 @@ export function ConnectMailSheet({ visible, onClose, onSuccess }: Props) {
|
||||
{t('mail.form_privacy_note')}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
</View>
|
||||
}
|
||||
onComplete={() => setFieldsComplete(true)}
|
||||
>
|
||||
{/* Fehler */}
|
||||
{(formError ?? (connectError ? t(humanizeMailError(connectError)) : null)) && (
|
||||
<Text
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Pressable, Text, View } from 'react-native';
|
||||
import { Text, TouchableOpacity, View } from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useColors } from '../../lib/theme';
|
||||
@ -81,12 +81,10 @@ export function MailEmptyState({ onConnectPress }: Props) {
|
||||
</View>
|
||||
|
||||
{/* CTA */}
|
||||
<Pressable
|
||||
<TouchableOpacity
|
||||
onPress={onConnectPress}
|
||||
style={({ pressed }) => ({
|
||||
opacity: pressed ? 0.85 : 1,
|
||||
alignSelf: 'stretch',
|
||||
})}
|
||||
activeOpacity={0.85}
|
||||
style={{ alignSelf: 'stretch' }}
|
||||
>
|
||||
<View style={{
|
||||
backgroundColor: '#007AFF',
|
||||
@ -99,7 +97,7 @@ export function MailEmptyState({ onConnectPress }: Props) {
|
||||
{t('mail.empty_state_cta')}
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user