Skip to content

Commit ccb6174

Browse files
committed
feat(core): introduce chatMode, isAccessoryField, and zero-jitter Android tracking (v1.2.0)
This major release introduces a zero-config chat UI mode and completely overhauls the cross-platform accessory view tracking logic for perfectly synchronized 120fps keyboard avoidance. Core API Changes: - FieldForm: Introduced chatMode={true}. Bypasses standard scroll padding layout calculations entirely. Automatically pins scroll content to the bottom moving in perfect lockstep with the keyboard via native scrollToEnd(). - FieldInput: Introduced isAccessoryField prop. Inputs nested inside the accessory toolbar are safely excluded from main viewport layout shift measurements, permanently resolving 'Error measuring text field' warnings. - FieldForm/types: Bumped default extraScrollPadding to 140 to better accommodate modern mobile keyboard heights. Animation & Performance: - FieldForm (Android): Completely eliminated Android layout 'popping' for accessory bars using a two-phase synchronization pattern. - FieldForm: Migrated accessory view to purely native-driven Animated.timing for bulletproof cross-platform parity. Docs: - Bumped package to v1.2.0, completely overhauled README.md dropping layout-heavy SVGs for lightweight side-by-side markdown comparison grids and native MP4 showcases.
1 parent bdca276 commit ccb6174

25 files changed

Lines changed: 1524 additions & 1094 deletions

README.md

Lines changed: 243 additions & 194 deletions
Large diffs are not rendered by default.

example/app/(tabs)/index.tsx

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ import {
2020
ActionButton,
2121
FeatureCard,
2222
SectionLabel,
23-
StatusPill,
24-
StyledInput,
23+
StyledInput
2524
} from '@/components/showcase';
2625
import {
2726
ShowcaseColors as C,
@@ -30,15 +29,13 @@ import {
3029
} from '@/constants/showcase-theme';
3130
import {
3231
FieldForm,
33-
useKeyboardState,
34-
type FieldFormHandle,
32+
type FieldFormHandle
3533
} from '../../../packages/react-native-fieldflow/src';
3634

3735
export default function HomeScreen() {
3836
const router = useRouter();
3937
const insets = useSafeAreaInsets();
4038
const formRef = useRef<FieldFormHandle>(null);
41-
const { height: kbHeight, visible: kbVisible } = useKeyboardState();
4239

4340
const [name, setName] = useState('');
4441
const [email, setEmail] = useState('');
@@ -84,15 +81,15 @@ export default function HomeScreen() {
8481

8582
{/* ── Demo Categories ──────────────────────── */}
8683
<View style={styles.section}>
87-
<SectionLabel
88-
title="Core Basics"
89-
description="The essentials of FieldFlow: chaining and avoidance"
84+
<SectionLabel
85+
title="Core Basics"
86+
description="The essentials of FieldFlow: chaining and avoidance"
9087
/>
9188
<View style={styles.navStack}>
9289
<FeatureCard
9390
icon="log-in-outline"
94-
title="01. Login (Hello World)"
95-
description="Simplest possible form 2 fields, no config"
91+
title="01. Login"
92+
description="Simplest possible form with 2 fields, no config"
9693
onPress={() => router.push('/demos/01-login')}
9794
/>
9895
<FeatureCard
@@ -110,16 +107,16 @@ export default function HomeScreen() {
110107
<FeatureCard
111108
icon="create-outline"
112109
title="04. Profile Edit"
113-
description="nextRef override & two-column layout"
110+
description="nextRef override & two column layout"
114111
onPress={() => router.push('/demos/04-profile')}
115112
/>
116113
</View>
117114
</View>
118115

119116
<View style={styles.section}>
120-
<SectionLabel
121-
title="Hooks & Behavior"
122-
description="Lifting UI and responding to keyboard state"
117+
<SectionLabel
118+
title="Hooks & Behavior"
119+
description="Lifting UI and responding to keyboard state"
123120
/>
124121
<View style={styles.navStack}>
125122
<FeatureCard
@@ -144,9 +141,9 @@ export default function HomeScreen() {
144141
</View>
145142

146143
<View style={styles.section}>
147-
<SectionLabel
148-
title="Advanced Layouts"
149-
description="Deep customisation and platform parity"
144+
<SectionLabel
145+
title="Advanced Layouts"
146+
description="Deep customisation and platform parity"
150147
/>
151148
<View style={styles.navStack}>
152149
<FeatureCard
@@ -173,6 +170,12 @@ export default function HomeScreen() {
173170
description="Chat composer with accessory toolbar"
174171
onPress={() => router.push('/demos/11-accessory-view')}
175172
/>
173+
<FeatureCard
174+
icon="layers-outline"
175+
title="12. Dynamic Forms"
176+
description="Adding and removing fields on the fly"
177+
onPress={() => router.push('/demos/12-dynamic-forms')}
178+
/>
176179
</View>
177180
</View>
178181

example/app/demos/01-login.tsx

Lines changed: 37 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import React from 'react';
2-
import { StyleSheet, Text, View, Platform, Alert } from 'react-native';
3-
import { Stack, useRouter } from 'expo-router';
41
import { Ionicons } from '@expo/vector-icons';
5-
import {
6-
FieldForm,
7-
FieldInput
2+
import { Stack, useRouter } from 'expo-router';
3+
import React from 'react';
4+
import { Alert, StyleSheet, Text, View } from 'react-native';
5+
import {
6+
FieldForm
87
} from '../../../packages/react-native-fieldflow/src';
9-
import { ShowcaseColors as C, ShowcaseSpacing, ShowcaseRadius } from '../../constants/showcase-theme';
10-
import { ActionButton, IconButton } from '../../components/showcase';
8+
import { ActionButton, IconButton, StyledInput } from '../../components/showcase';
9+
import { ShowcaseColors as C, ShowcaseRadius, ShowcaseSpacing } from '../../constants/showcase-theme';
1110

1211
export default function LoginDemo() {
1312
const router = useRouter();
@@ -18,61 +17,51 @@ export default function LoginDemo() {
1817

1918
return (
2019
<View style={styles.container}>
21-
<Stack.Screen
22-
options={{
20+
<Stack.Screen
21+
options={{
2322
headerLeft: () => (
24-
<IconButton
25-
icon="chevron-back"
26-
onPress={() => router.back()}
23+
<IconButton
24+
icon="chevron-back"
25+
onPress={() => router.back()}
2726
/>
2827
),
29-
}}
28+
}}
3029
/>
3130

32-
<FieldForm
31+
<FieldForm
3332
onSubmit={handleSubmit}
34-
extraScrollPadding={100}
33+
extraScrollPadding={140}
3534
keyboardVerticalOffset={0}
3635
scrollViewProps={{
3736
contentContainerStyle: styles.scrollContent,
38-
keyboardDismissMode: 'interactive',
3937
}}
4038
>
4139
<View style={styles.header}>
42-
<View style={styles.iconCircle}>
43-
<Ionicons name="lock-closed" size={32} color={C.accent} />
44-
</View>
45-
<Text style={styles.title}>Welcome back</Text>
46-
<Text style={styles.subtitle}>The smallest possible working form.</Text>
40+
<View style={styles.iconCircle}>
41+
<Ionicons name="lock-closed" size={32} color={C.accent} />
42+
</View>
43+
<Text style={styles.title}>Welcome back</Text>
44+
<Text style={styles.subtitle}>The smallest possible working form.</Text>
4745
</View>
4846

4947
<View style={styles.form}>
50-
<View style={styles.inputWrapper}>
51-
<Text style={styles.label}>Email Address</Text>
52-
<FieldInput
53-
placeholder="john@example.com"
54-
keyboardType="email-address"
55-
autoCapitalize="none"
56-
textContentType="emailAddress"
57-
style={styles.input}
58-
placeholderTextColor={C.textTertiary}
59-
/>
60-
</View>
61-
62-
<View style={styles.inputWrapper}>
63-
<Text style={styles.label}>Password</Text>
64-
<FieldInput
65-
placeholder="••••••••"
66-
secureTextEntry
67-
textContentType="password"
68-
style={styles.input}
69-
placeholderTextColor={C.textTertiary}
70-
/>
71-
</View>
72-
73-
<ActionButton
74-
title="Sign in"
75-
onPress={handleSubmit}
48+
<StyledInput
49+
icon="mail-outline"
50+
label="Email address"
51+
placeholder="john@example.com"
52+
keyboardType="email-address"
53+
autoCapitalize="none"
54+
/>
55+
<StyledInput
56+
icon="lock-closed-outline"
57+
label="Password"
58+
placeholder="john@example.com"
59+
keyboardType="visible-password"
60+
autoCapitalize="none"
61+
/>
62+
<ActionButton
63+
title="Sign in"
64+
onPress={handleSubmit}
7665
style={styles.submitButton}
7766
/>
7867

example/app/demos/02-signup.tsx

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import React, { useState } from 'react';
2-
import { StyleSheet, Text, View, Alert } from 'react-native';
31
import { Stack, useRouter } from 'expo-router';
4-
import {
5-
FieldForm,
6-
FieldInput
2+
import React, { useState } from 'react';
3+
import { Alert, StyleSheet, Text, View } from 'react-native';
4+
import {
5+
FieldForm,
6+
FieldInput
77
} from '../../../packages/react-native-fieldflow/src';
8-
import { ShowcaseColors as C, ShowcaseSpacing, ShowcaseRadius } from '../../constants/showcase-theme';
98
import { ActionButton, IconButton } from '../../components/showcase';
9+
import { ShowcaseColors as C, ShowcaseRadius, ShowcaseSpacing } from '../../constants/showcase-theme';
1010

1111
export default function SignupDemo() {
1212
const router = useRouter();
@@ -20,10 +20,10 @@ export default function SignupDemo() {
2020
<View style={styles.indicatorContainer}>
2121
<View style={styles.stepsRow}>
2222
{[1, 2, 3, 4, 5].map((step, idx) => (
23-
<View
24-
key={step}
23+
<View
24+
key={step}
2525
style={[
26-
styles.stepCircle,
26+
styles.stepCircle,
2727
activeIndex === idx && styles.stepCircleActive
2828
]}
2929
>
@@ -40,50 +40,52 @@ export default function SignupDemo() {
4040

4141
return (
4242
<View style={styles.container}>
43-
<Stack.Screen
44-
options={{
43+
<Stack.Screen
44+
options={{
4545
headerLeft: () => (
46-
<IconButton
47-
icon="chevron-back"
48-
onPress={() => router.back()}
46+
<IconButton
47+
icon="chevron-back"
48+
onPress={() => router.back()}
4949
/>
5050
),
51-
}}
51+
}}
5252
/>
5353

54-
<FieldForm
54+
<FieldForm
5555
onSubmit={handleSubmit}
56-
extraScrollPadding={100}
56+
extraScrollPadding={140}
5757
keyboardVerticalOffset={0}
5858
scrollViewProps={{
5959
contentContainerStyle: styles.scrollContent,
6060
keyboardDismissMode: 'interactive',
6161
}}
6262
>
6363
<View style={styles.header}>
64-
<Text style={styles.title}>Create Account</Text>
65-
<Text style={styles.subtitle}>Demonstrating the zero-ref focus chain.</Text>
64+
<Text style={styles.title}>Create Account</Text>
65+
<Text style={styles.subtitle}>Demonstrating the zero-ref focus chain.</Text>
6666
</View>
6767

6868
{renderStepIndicator()}
6969

7070
<View style={styles.form}>
7171
<View style={styles.inputWrapper}>
7272
<Text style={styles.label}>Full Name</Text>
73-
<FieldInput
73+
<FieldInput
7474
placeholder="Jane Doe"
7575
textContentType="name"
76+
placeholderTextColor={styles.placeholderColor.color}
7677
onFocus={() => setActiveIndex(0)}
7778
style={[styles.input, activeIndex === 0 && styles.inputActive]}
7879
/>
7980
</View>
8081

8182
<View style={styles.inputWrapper}>
8283
<Text style={styles.label}>Email Address</Text>
83-
<FieldInput
84+
<FieldInput
8485
placeholder="jane@example.com"
8586
keyboardType="email-address"
8687
autoCapitalize="none"
88+
placeholderTextColor={styles.placeholderColor.color}
8789
textContentType="emailAddress"
8890
onFocus={() => setActiveIndex(1)}
8991
style={[styles.input, activeIndex === 1 && styles.inputActive]}
@@ -92,9 +94,10 @@ export default function SignupDemo() {
9294

9395
<View style={styles.inputWrapper}>
9496
<Text style={styles.label}>Phone Number</Text>
95-
<FieldInput
97+
<FieldInput
9698
placeholder="+1 (555) 000-0000"
9799
keyboardType="phone-pad"
100+
placeholderTextColor={styles.placeholderColor.color}
98101
textContentType="telephoneNumber"
99102
onFocus={() => setActiveIndex(2)}
100103
style={[styles.input, activeIndex === 2 && styles.inputActive]}
@@ -103,32 +106,34 @@ export default function SignupDemo() {
103106

104107
<View style={styles.inputWrapper}>
105108
<Text style={styles.label}>Password</Text>
106-
<FieldInput
109+
<FieldInput
107110
placeholder="Choose a password"
108-
secureTextEntry
109-
textContentType="newPassword"
111+
// secureTextEntry
112+
placeholderTextColor={styles.placeholderColor.color}
113+
// textContentType="newPassword"
110114
onFocus={() => setActiveIndex(3)}
111115
style={[styles.input, activeIndex === 3 && styles.inputActive]}
112116
/>
113117
</View>
114118

115119
<View style={styles.inputWrapper}>
116120
<Text style={styles.label}>Confirm Password</Text>
117-
<FieldInput
121+
<FieldInput
118122
placeholder="Repeat password"
119-
secureTextEntry
120-
textContentType="newPassword"
123+
// secureTextEntry
124+
placeholderTextColor={styles.placeholderColor.color}
125+
// textContentType="newPassword"
121126
onFocus={() => setActiveIndex(4)}
122127
style={[styles.input, activeIndex === 4 && styles.inputActive]}
123128
/>
124129
</View>
125130

126-
<ActionButton
127-
title="Create Account"
128-
onPress={handleSubmit}
131+
<ActionButton
132+
title="Create Account"
133+
onPress={handleSubmit}
129134
style={styles.submitButton}
130135
/>
131-
136+
132137
<View style={styles.callout}>
133138
<Text style={styles.calloutTitle}>The Selling Point</Text>
134139
<Text style={styles.calloutText}>
@@ -234,6 +239,9 @@ const styles = StyleSheet.create({
234239
borderWidth: 1,
235240
borderColor: C.borderSubtle,
236241
},
242+
placeholderColor: {
243+
color: C.textSecondary,
244+
},
237245
inputActive: {
238246
borderColor: C.accent,
239247
backgroundColor: C.bgPrimary,

0 commit comments

Comments
 (0)