import { ReactNode, useState } from 'react'; import { Keyboard, ScrollView, Text, TextInput, TouchableOpacity, View, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { useColors } from '../lib/theme'; export type SheetField = { key: string; label: string; placeholder?: string; value: string; onChangeText: (v: string) => void; validate?: (v: string) => string | undefined; normalize?: (v: string) => string; keyboardType?: TextInput['props']['keyboardType']; secureTextEntry?: boolean; autoCapitalize?: TextInput['props']['autoCapitalize']; autoCorrect?: boolean; suffix?: ReactNode; }; 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; }; /** * Progressives Multi-Input-Pattern für FormSheet-Inhalte. * * Jeweils ein Feld ist aktiv (TextInput + →/✓-Button). Bereits ausgefüllte * Felder wandern als antippbare Chips nach oben. Tap auf Chip → zurück zum * Editieren. Nach dem letzten Feld: Keyboard.dismiss() + children werden sichtbar. * * Wird als `children` von `` benutzt. */ export function SheetFieldStack({ fields, intro, children, onComplete }: Props) { const colors = useColors(); const [activeIndex, setActiveIndex] = useState(0); const [fieldErrors, setFieldErrors] = useState>({}); const allDone = activeIndex >= fields.length; function advanceOrFinish() { const field = fields[activeIndex]; if (!field) return; const error = field.validate?.(field.value); if (error) { setFieldErrors((prev) => ({ ...prev, [field.key]: error })); return; } const normalized = field.normalize ? field.normalize(field.value) : field.value; if (normalized !== field.value) { field.onChangeText(normalized); } setFieldErrors((prev) => { const next = { ...prev }; delete next[field.key]; return next; }); if (activeIndex === fields.length - 1) { Keyboard.dismiss(); setActiveIndex(fields.length); onComplete?.(); } else { setActiveIndex((i) => i + 1); } } function goToField(index: number) { setActiveIndex(index); } const isLast = activeIndex === fields.length - 1; return ( {/* Intro: immer sichtbar, schrumpft bei wenig Platz (Tastatur offen) */} {intro != null && ( {intro} )} {/* Chips + aktives Feld + Post-Completion-Inhalt */} {/* Abgeschlossene Felder als Chips */} {fields.slice(0, activeIndex).map((field, index) => ( goToField(index)} style={{ flexDirection: 'row', alignItems: 'center', backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 12, paddingHorizontal: 14, paddingVertical: 10, gap: 10, }} > {field.label} {field.secureTextEntry ? '••••••••' : field.value} ))} {/* Aktives Feld */} {!allDone && ( {fields[activeIndex].label} { fields[activeIndex].onChangeText(v); if (fieldErrors[fields[activeIndex].key]) { setFieldErrors((prev) => { const next = { ...prev }; delete next[fields[activeIndex].key]; return next; }); } }} placeholder={fields[activeIndex].placeholder} placeholderTextColor={colors.textMuted} keyboardType={fields[activeIndex].keyboardType ?? 'default'} secureTextEntry={fields[activeIndex].secureTextEntry} autoCapitalize={fields[activeIndex].autoCapitalize ?? 'sentences'} autoCorrect={fields[activeIndex].autoCorrect ?? true} returnKeyType={isLast ? 'done' : 'next'} onSubmitEditing={advanceOrFinish} blurOnSubmit={false} style={{ flex: 1, paddingVertical: 12, fontSize: 15, fontFamily: 'Nunito_400Regular', color: colors.text, }} /> {fields[activeIndex].suffix} {fieldErrors[fields[activeIndex].key] && ( {fieldErrors[fields[activeIndex].key]} )} )} {/* Rest des Formulars — sichtbar wenn alle Felder durch */} {allDone && children} ); }