rn-inputforge 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/README.md +224 -19
  2. package/lib/commonjs/components/FieldFlowCheckbox/FieldFlowCheckbox.js +131 -0
  3. package/lib/commonjs/components/FieldFlowCheckbox/FieldFlowCheckbox.js.map +1 -0
  4. package/lib/commonjs/components/FieldFlowCheckbox/index.js +13 -0
  5. package/lib/commonjs/components/FieldFlowCheckbox/index.js.map +1 -0
  6. package/lib/commonjs/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.js +536 -0
  7. package/lib/commonjs/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.js.map +1 -0
  8. package/lib/commonjs/components/FieldFlowDateTimePicker/index.js +13 -0
  9. package/lib/commonjs/components/FieldFlowDateTimePicker/index.js.map +1 -0
  10. package/lib/commonjs/components/FieldFlowDateTimePicker/types.js +6 -0
  11. package/lib/commonjs/components/FieldFlowDateTimePicker/types.js.map +1 -0
  12. package/lib/commonjs/components/FieldFlowDropdown/FieldFlowDropdown.js +24 -9
  13. package/lib/commonjs/components/FieldFlowDropdown/FieldFlowDropdown.js.map +1 -1
  14. package/lib/commonjs/components/FieldFlowInput/FieldFlowInput.js +55 -24
  15. package/lib/commonjs/components/FieldFlowInput/FieldFlowInput.js.map +1 -1
  16. package/lib/commonjs/components/FieldFlowInput/styles.js +32 -11
  17. package/lib/commonjs/components/FieldFlowInput/styles.js.map +1 -1
  18. package/lib/commonjs/components/FieldFlowMaskedInput/FieldFlowMaskedInput.js +66 -0
  19. package/lib/commonjs/components/FieldFlowMaskedInput/FieldFlowMaskedInput.js.map +1 -0
  20. package/lib/commonjs/components/FieldFlowMaskedInput/index.js +13 -0
  21. package/lib/commonjs/components/FieldFlowMaskedInput/index.js.map +1 -0
  22. package/lib/commonjs/components/FieldFlowMaskedInput/types.js +6 -0
  23. package/lib/commonjs/components/FieldFlowMaskedInput/types.js.map +1 -0
  24. package/lib/commonjs/components/FieldFlowOTP/FieldFlowOTP.js +255 -0
  25. package/lib/commonjs/components/FieldFlowOTP/FieldFlowOTP.js.map +1 -0
  26. package/lib/commonjs/components/FieldFlowOTP/index.js +13 -0
  27. package/lib/commonjs/components/FieldFlowOTP/index.js.map +1 -0
  28. package/lib/commonjs/components/FieldFlowOTP/types.js +6 -0
  29. package/lib/commonjs/components/FieldFlowOTP/types.js.map +1 -0
  30. package/lib/commonjs/components/FieldFlowRadio/FieldFlowRadio.js +164 -0
  31. package/lib/commonjs/components/FieldFlowRadio/FieldFlowRadio.js.map +1 -0
  32. package/lib/commonjs/components/FieldFlowRadio/index.js +13 -0
  33. package/lib/commonjs/components/FieldFlowRadio/index.js.map +1 -0
  34. package/lib/commonjs/components/FieldFlowSwitch/FieldFlowSwitch.js +152 -0
  35. package/lib/commonjs/components/FieldFlowSwitch/FieldFlowSwitch.js.map +1 -0
  36. package/lib/commonjs/components/FieldFlowSwitch/index.js +13 -0
  37. package/lib/commonjs/components/FieldFlowSwitch/index.js.map +1 -0
  38. package/lib/commonjs/hooks/index.js.map +1 -1
  39. package/lib/commonjs/hooks/useFieldFlowForm.js +146 -31
  40. package/lib/commonjs/hooks/useFieldFlowForm.js.map +1 -1
  41. package/lib/commonjs/icons/Icon.js +719 -30
  42. package/lib/commonjs/icons/Icon.js.map +1 -1
  43. package/lib/commonjs/icons/index.js +7 -0
  44. package/lib/commonjs/icons/index.js.map +1 -1
  45. package/lib/commonjs/icons/renderIcon.js +24 -0
  46. package/lib/commonjs/icons/renderIcon.js.map +1 -0
  47. package/lib/commonjs/index.js +127 -0
  48. package/lib/commonjs/index.js.map +1 -1
  49. package/lib/commonjs/utils/masks.js +130 -0
  50. package/lib/commonjs/utils/masks.js.map +1 -0
  51. package/lib/commonjs/validation/index.js +25 -0
  52. package/lib/commonjs/validation/index.js.map +1 -1
  53. package/lib/commonjs/validation/zodAdapter.js +91 -0
  54. package/lib/commonjs/validation/zodAdapter.js.map +1 -0
  55. package/lib/module/components/FieldFlowCheckbox/FieldFlowCheckbox.js +126 -0
  56. package/lib/module/components/FieldFlowCheckbox/FieldFlowCheckbox.js.map +1 -0
  57. package/lib/module/components/FieldFlowCheckbox/index.js +4 -0
  58. package/lib/module/components/FieldFlowCheckbox/index.js.map +1 -0
  59. package/lib/module/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.js +531 -0
  60. package/lib/module/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.js.map +1 -0
  61. package/lib/module/components/FieldFlowDateTimePicker/index.js +4 -0
  62. package/lib/module/components/FieldFlowDateTimePicker/index.js.map +1 -0
  63. package/lib/module/components/FieldFlowDateTimePicker/types.js +4 -0
  64. package/lib/module/components/FieldFlowDateTimePicker/types.js.map +1 -0
  65. package/lib/module/components/FieldFlowDropdown/FieldFlowDropdown.js +24 -9
  66. package/lib/module/components/FieldFlowDropdown/FieldFlowDropdown.js.map +1 -1
  67. package/lib/module/components/FieldFlowInput/FieldFlowInput.js +59 -28
  68. package/lib/module/components/FieldFlowInput/FieldFlowInput.js.map +1 -1
  69. package/lib/module/components/FieldFlowInput/styles.js +32 -11
  70. package/lib/module/components/FieldFlowInput/styles.js.map +1 -1
  71. package/lib/module/components/FieldFlowMaskedInput/FieldFlowMaskedInput.js +61 -0
  72. package/lib/module/components/FieldFlowMaskedInput/FieldFlowMaskedInput.js.map +1 -0
  73. package/lib/module/components/FieldFlowMaskedInput/index.js +4 -0
  74. package/lib/module/components/FieldFlowMaskedInput/index.js.map +1 -0
  75. package/lib/module/components/FieldFlowMaskedInput/types.js +4 -0
  76. package/lib/module/components/FieldFlowMaskedInput/types.js.map +1 -0
  77. package/lib/module/components/FieldFlowOTP/FieldFlowOTP.js +250 -0
  78. package/lib/module/components/FieldFlowOTP/FieldFlowOTP.js.map +1 -0
  79. package/lib/module/components/FieldFlowOTP/index.js +4 -0
  80. package/lib/module/components/FieldFlowOTP/index.js.map +1 -0
  81. package/lib/module/components/FieldFlowOTP/types.js +4 -0
  82. package/lib/module/components/FieldFlowOTP/types.js.map +1 -0
  83. package/lib/module/components/FieldFlowRadio/FieldFlowRadio.js +159 -0
  84. package/lib/module/components/FieldFlowRadio/FieldFlowRadio.js.map +1 -0
  85. package/lib/module/components/FieldFlowRadio/index.js +4 -0
  86. package/lib/module/components/FieldFlowRadio/index.js.map +1 -0
  87. package/lib/module/components/FieldFlowSwitch/FieldFlowSwitch.js +147 -0
  88. package/lib/module/components/FieldFlowSwitch/FieldFlowSwitch.js.map +1 -0
  89. package/lib/module/components/FieldFlowSwitch/index.js +4 -0
  90. package/lib/module/components/FieldFlowSwitch/index.js.map +1 -0
  91. package/lib/module/hooks/index.js.map +1 -1
  92. package/lib/module/hooks/useFieldFlowForm.js +147 -32
  93. package/lib/module/hooks/useFieldFlowForm.js.map +1 -1
  94. package/lib/module/icons/Icon.js +720 -31
  95. package/lib/module/icons/Icon.js.map +1 -1
  96. package/lib/module/icons/index.js +1 -0
  97. package/lib/module/icons/index.js.map +1 -1
  98. package/lib/module/icons/renderIcon.js +19 -0
  99. package/lib/module/icons/renderIcon.js.map +1 -0
  100. package/lib/module/index.js +11 -3
  101. package/lib/module/index.js.map +1 -1
  102. package/lib/module/utils/masks.js +118 -0
  103. package/lib/module/utils/masks.js.map +1 -0
  104. package/lib/module/validation/index.js +1 -0
  105. package/lib/module/validation/index.js.map +1 -1
  106. package/lib/module/validation/zodAdapter.js +84 -0
  107. package/lib/module/validation/zodAdapter.js.map +1 -0
  108. package/lib/typescript/components/FieldFlowCheckbox/FieldFlowCheckbox.d.ts +36 -0
  109. package/lib/typescript/components/FieldFlowCheckbox/FieldFlowCheckbox.d.ts.map +1 -0
  110. package/lib/typescript/components/FieldFlowCheckbox/index.d.ts +3 -0
  111. package/lib/typescript/components/FieldFlowCheckbox/index.d.ts.map +1 -0
  112. package/lib/typescript/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.d.ts +4 -0
  113. package/lib/typescript/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.d.ts.map +1 -0
  114. package/lib/typescript/components/FieldFlowDateTimePicker/index.d.ts +3 -0
  115. package/lib/typescript/components/FieldFlowDateTimePicker/index.d.ts.map +1 -0
  116. package/lib/typescript/components/FieldFlowDateTimePicker/types.d.ts +55 -0
  117. package/lib/typescript/components/FieldFlowDateTimePicker/types.d.ts.map +1 -0
  118. package/lib/typescript/components/FieldFlowDropdown/FieldFlowDropdown.d.ts.map +1 -1
  119. package/lib/typescript/components/FieldFlowDropdown/types.d.ts +7 -1
  120. package/lib/typescript/components/FieldFlowDropdown/types.d.ts.map +1 -1
  121. package/lib/typescript/components/FieldFlowInput/FieldFlowInput.d.ts.map +1 -1
  122. package/lib/typescript/components/FieldFlowInput/styles.d.ts +19 -2
  123. package/lib/typescript/components/FieldFlowInput/styles.d.ts.map +1 -1
  124. package/lib/typescript/components/FieldFlowInput/types.d.ts +24 -5
  125. package/lib/typescript/components/FieldFlowInput/types.d.ts.map +1 -1
  126. package/lib/typescript/components/FieldFlowMaskedInput/FieldFlowMaskedInput.d.ts +5 -0
  127. package/lib/typescript/components/FieldFlowMaskedInput/FieldFlowMaskedInput.d.ts.map +1 -0
  128. package/lib/typescript/components/FieldFlowMaskedInput/index.d.ts +3 -0
  129. package/lib/typescript/components/FieldFlowMaskedInput/index.d.ts.map +1 -0
  130. package/lib/typescript/components/FieldFlowMaskedInput/types.d.ts +20 -0
  131. package/lib/typescript/components/FieldFlowMaskedInput/types.d.ts.map +1 -0
  132. package/lib/typescript/components/FieldFlowOTP/FieldFlowOTP.d.ts +4 -0
  133. package/lib/typescript/components/FieldFlowOTP/FieldFlowOTP.d.ts.map +1 -0
  134. package/lib/typescript/components/FieldFlowOTP/index.d.ts +3 -0
  135. package/lib/typescript/components/FieldFlowOTP/index.d.ts.map +1 -0
  136. package/lib/typescript/components/FieldFlowOTP/types.d.ts +52 -0
  137. package/lib/typescript/components/FieldFlowOTP/types.d.ts.map +1 -0
  138. package/lib/typescript/components/FieldFlowRadio/FieldFlowRadio.d.ts +39 -0
  139. package/lib/typescript/components/FieldFlowRadio/FieldFlowRadio.d.ts.map +1 -0
  140. package/lib/typescript/components/FieldFlowRadio/index.d.ts +3 -0
  141. package/lib/typescript/components/FieldFlowRadio/index.d.ts.map +1 -0
  142. package/lib/typescript/components/FieldFlowSwitch/FieldFlowSwitch.d.ts +31 -0
  143. package/lib/typescript/components/FieldFlowSwitch/FieldFlowSwitch.d.ts.map +1 -0
  144. package/lib/typescript/components/FieldFlowSwitch/index.d.ts +3 -0
  145. package/lib/typescript/components/FieldFlowSwitch/index.d.ts.map +1 -0
  146. package/lib/typescript/hooks/index.d.ts +1 -1
  147. package/lib/typescript/hooks/index.d.ts.map +1 -1
  148. package/lib/typescript/hooks/useFieldFlowForm.d.ts +46 -7
  149. package/lib/typescript/hooks/useFieldFlowForm.d.ts.map +1 -1
  150. package/lib/typescript/icons/Icon.d.ts +2 -2
  151. package/lib/typescript/icons/Icon.d.ts.map +1 -1
  152. package/lib/typescript/icons/index.d.ts +2 -0
  153. package/lib/typescript/icons/index.d.ts.map +1 -1
  154. package/lib/typescript/icons/renderIcon.d.ts +6 -0
  155. package/lib/typescript/icons/renderIcon.d.ts.map +1 -0
  156. package/lib/typescript/index.d.ts +20 -6
  157. package/lib/typescript/index.d.ts.map +1 -1
  158. package/lib/typescript/types/index.d.ts +4 -0
  159. package/lib/typescript/types/index.d.ts.map +1 -1
  160. package/lib/typescript/utils/masks.d.ts +37 -0
  161. package/lib/typescript/utils/masks.d.ts.map +1 -0
  162. package/lib/typescript/validation/index.d.ts +2 -0
  163. package/lib/typescript/validation/index.d.ts.map +1 -1
  164. package/lib/typescript/validation/zodAdapter.d.ts +53 -0
  165. package/lib/typescript/validation/zodAdapter.d.ts.map +1 -0
  166. package/package.json +27 -5
  167. package/src/components/FieldFlowCheckbox/FieldFlowCheckbox.tsx +174 -0
  168. package/src/components/FieldFlowCheckbox/index.ts +2 -0
  169. package/src/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.tsx +585 -0
  170. package/src/components/FieldFlowDateTimePicker/index.ts +2 -0
  171. package/src/components/FieldFlowDateTimePicker/types.ts +57 -0
  172. package/src/components/FieldFlowDropdown/FieldFlowDropdown.tsx +17 -7
  173. package/src/components/FieldFlowDropdown/types.ts +8 -1
  174. package/src/components/FieldFlowInput/FieldFlowInput.tsx +69 -26
  175. package/src/components/FieldFlowInput/styles.ts +41 -6
  176. package/src/components/FieldFlowInput/types.ts +24 -5
  177. package/src/components/FieldFlowMaskedInput/FieldFlowMaskedInput.tsx +76 -0
  178. package/src/components/FieldFlowMaskedInput/index.ts +2 -0
  179. package/src/components/FieldFlowMaskedInput/types.ts +22 -0
  180. package/src/components/FieldFlowOTP/FieldFlowOTP.tsx +305 -0
  181. package/src/components/FieldFlowOTP/index.ts +2 -0
  182. package/src/components/FieldFlowOTP/types.ts +57 -0
  183. package/src/components/FieldFlowRadio/FieldFlowRadio.tsx +215 -0
  184. package/src/components/FieldFlowRadio/index.ts +2 -0
  185. package/src/components/FieldFlowSwitch/FieldFlowSwitch.tsx +172 -0
  186. package/src/components/FieldFlowSwitch/index.ts +2 -0
  187. package/src/hooks/index.ts +3 -0
  188. package/src/hooks/useFieldFlowForm.ts +209 -33
  189. package/src/icons/Icon.tsx +451 -5
  190. package/src/icons/index.ts +2 -0
  191. package/src/icons/renderIcon.tsx +17 -0
  192. package/src/index.ts +47 -4
  193. package/src/types/index.ts +5 -0
  194. package/src/utils/masks.ts +137 -0
  195. package/src/validation/index.ts +7 -0
  196. package/src/validation/zodAdapter.ts +103 -0
@@ -1,5 +1,6 @@
1
1
  import type { ViewStyle, TextStyle } from 'react-native';
2
- import type { DeepPartial, FieldFlowOption, FieldFlowTheme } from '../../types';
2
+ import type { DeepPartial, FieldFlowOption, FieldFlowTheme, FieldFlowVariant } from '../../types';
3
+ import type { FieldFlowIconSource } from '../../icons';
3
4
 
4
5
  export type FieldFlowDropdownPresentation = 'bottomSheet' | 'popup' | 'inline';
5
6
 
@@ -26,6 +27,12 @@ interface BaseDropdownProps {
26
27
  maxModalHeight?: number;
27
28
  clearable?: boolean;
28
29
 
30
+ /** Built-in icon name or custom React node on the trigger. */
31
+ leftIcon?: FieldFlowIconSource;
32
+ rightIcon?: FieldFlowIconSource;
33
+ iconSize?: number;
34
+ variant?: FieldFlowVariant;
35
+
29
36
  containerStyle?: ViewStyle;
30
37
  triggerStyle?: ViewStyle;
31
38
  labelStyle?: TextStyle;
@@ -1,29 +1,26 @@
1
- import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
2
- import { Pressable, Text, TextInput, View } from 'react-native';
1
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
2
+ import { Animated, Pressable, Text, TextInput, View } from 'react-native';
3
3
  import { useFieldFlowTheme, mergeDeep } from '../../theme/ThemeProvider';
4
4
  import { defaultTheme } from '../../theme/defaultTheme';
5
5
  import { useFieldState } from '../../hooks/useFieldState';
6
6
  import { resolveFieldConfig, implicitRulesForType } from '../../utils/keyboardTypes';
7
7
  import { sanitizeNumeric, sanitizePhone, truncate } from '../../utils/formatters';
8
8
  import { runRules } from '../../validation/validators';
9
- import { FieldFlowIcon } from '../../icons/Icon';
10
- import { createStyles, iconColorFor } from './styles';
9
+ import { renderFieldFlowIcon } from '../../icons/renderIcon';
10
+ import { createStyles, iconColorFor, labelColorFor } from './styles';
11
11
  import type { FieldFlowInputProps } from './types';
12
12
 
13
- function renderIconNode(
14
- icon: FieldFlowInputProps['leftIcon'],
15
- color: string,
16
- ): React.ReactNode {
17
- if (!icon) return null;
18
- if (typeof icon === 'string') {
19
- return <FieldFlowIcon name={icon as any} color={color} size={18} />;
20
- }
21
- return icon;
22
- }
13
+ const DEFAULT_ICONS: Partial<Record<NonNullable<FieldFlowInputProps['type']>, FieldFlowInputProps['leftIcon']>> = {
14
+ search: 'search',
15
+ email: 'mail',
16
+ phone: 'phone',
17
+ password: 'lock',
18
+ };
23
19
 
24
20
  export const FieldFlowInput = forwardRef<TextInput, FieldFlowInputProps>(function FieldFlowInput(
25
21
  {
26
22
  type = 'text',
23
+ variant = 'outlined',
27
24
  label,
28
25
  helperText,
29
26
  errorText,
@@ -46,16 +43,20 @@ export const FieldFlowInput = forwardRef<TextInput, FieldFlowInputProps>(functio
46
43
  rightIcon,
47
44
  onLeftIconPress,
48
45
  onRightIconPress,
46
+ iconSize = 18,
49
47
  clearable,
50
48
  maxLength,
51
49
  showCharacterCount,
52
50
  onSubmitEditing,
53
51
  containerStyle,
52
+ inputContainerStyle,
54
53
  inputStyle,
55
54
  labelStyle,
56
55
  helperStyle,
56
+ errorStyle,
57
57
  theme: themeOverride,
58
58
  testID,
59
+ placeholder,
59
60
  ...rest
60
61
  },
61
62
  forwardedRef,
@@ -94,11 +95,24 @@ export const FieldFlowInput = forwardRef<TextInput, FieldFlowInputProps>(functio
94
95
  });
95
96
 
96
97
  const styles = useMemo(
97
- () => createStyles(theme, state, fieldConfig.multiline && type !== 'chat'),
98
- [theme, state, fieldConfig.multiline, type],
98
+ () => createStyles(theme, state, fieldConfig.multiline && type !== 'chat', variant),
99
+ [theme, state, fieldConfig.multiline, type, variant],
99
100
  );
100
101
  const iconColor = iconColorFor(theme, state);
101
102
 
103
+ const isFloating = variant === 'floating';
104
+ const floatUp = isFocused || currentValue.length > 0;
105
+ const floatAnim = useRef(new Animated.Value(floatUp ? 1 : 0)).current;
106
+
107
+ useEffect(() => {
108
+ if (!isFloating) return;
109
+ Animated.timing(floatAnim, {
110
+ toValue: floatUp ? 1 : 0,
111
+ duration: theme.animation.duration,
112
+ useNativeDriver: false,
113
+ }).start();
114
+ }, [floatAnim, floatUp, isFloating, theme.animation.duration]);
115
+
102
116
  const runValidation = useCallback(
103
117
  (nextValue: string) => {
104
118
  const result = runRules(nextValue, effectiveRules);
@@ -144,11 +158,12 @@ export const FieldFlowInput = forwardRef<TextInput, FieldFlowInputProps>(functio
144
158
  const isClearable = clearable ?? type === 'search';
145
159
  const showClearButton = isClearable && currentValue.length > 0 && !disabled && !readOnly && !loading;
146
160
 
147
- const resolvedLeftIcon = leftIcon ?? (type === 'search' ? 'search' : undefined);
148
- let resolvedRightIcon = rightIcon;
161
+ const resolvedLeftIcon = leftIcon === null ? undefined : leftIcon !== undefined ? leftIcon : DEFAULT_ICONS[type];
162
+ let resolvedRightIcon: FieldFlowInputProps['rightIcon'] | undefined =
163
+ rightIcon === null ? undefined : rightIcon;
149
164
  let rightIconPress = onRightIconPress;
150
165
 
151
- if (!resolvedRightIcon) {
166
+ if (rightIcon === undefined) {
152
167
  if (loading) {
153
168
  resolvedRightIcon = 'spinner';
154
169
  } else if (type === 'password') {
@@ -164,24 +179,50 @@ export const FieldFlowInput = forwardRef<TextInput, FieldFlowInputProps>(functio
164
179
  }
165
180
  }
166
181
 
182
+ const floatingLabelStyle = {
183
+ top: floatAnim.interpolate({
184
+ inputRange: [0, 1],
185
+ outputRange: [
186
+ theme.spacing.paddingVertical + (theme.typography.fontSize - theme.typography.labelFontSize) / 2 + 2,
187
+ -theme.typography.labelFontSize / 2 - 2,
188
+ ],
189
+ }),
190
+ fontSize: floatAnim.interpolate({
191
+ inputRange: [0, 1],
192
+ outputRange: [theme.typography.fontSize, theme.typography.labelFontSize],
193
+ }),
194
+ color: labelColorFor(theme, state),
195
+ left: theme.spacing.paddingHorizontal + (resolvedLeftIcon ? iconSize + theme.spacing.gap : 0),
196
+ };
197
+
198
+ const showExternalLabel = Boolean(label) && !isFloating;
199
+
167
200
  return (
168
201
  <View style={[styles.wrapper, containerStyle]} testID={testID}>
169
- {label ? (
202
+ {showExternalLabel ? (
170
203
  <Text style={[styles.label, labelStyle]}>
171
204
  {label}
172
205
  {required ? ' *' : ''}
173
206
  </Text>
174
207
  ) : null}
175
208
 
176
- <View style={styles.inputContainer}>
209
+ <View style={[styles.inputContainer, inputContainerStyle]}>
210
+ {isFloating && label ? (
211
+ <Animated.Text style={[styles.floatingLabel, floatingLabelStyle, labelStyle]} pointerEvents="none">
212
+ {label}
213
+ {required ? ' *' : ''}
214
+ </Animated.Text>
215
+ ) : null}
216
+
177
217
  {resolvedLeftIcon ? (
178
218
  <Pressable
179
219
  onPress={onLeftIconPress}
180
220
  disabled={!onLeftIconPress}
181
221
  style={[styles.iconSlot, styles.iconSlotLeft]}
182
222
  hitSlop={8}
223
+ accessibilityRole={onLeftIconPress ? 'button' : undefined}
183
224
  >
184
- {renderIconNode(resolvedLeftIcon, iconColor)}
225
+ {renderFieldFlowIcon(resolvedLeftIcon, iconColor, iconSize)}
185
226
  </Pressable>
186
227
  ) : null}
187
228
 
@@ -193,6 +234,7 @@ export const FieldFlowInput = forwardRef<TextInput, FieldFlowInputProps>(functio
193
234
  onBlur={handleBlur}
194
235
  onSubmitEditing={onSubmitEditing}
195
236
  editable={!disabled && !readOnly && !loading}
237
+ placeholder={isFloating ? (floatUp ? placeholder : undefined) : placeholder}
196
238
  placeholderTextColor={theme.colors.placeholder}
197
239
  keyboardType={fieldConfig.keyboardType}
198
240
  secureTextEntry={fieldConfig.secureTextEntry && !showPassword}
@@ -211,8 +253,9 @@ export const FieldFlowInput = forwardRef<TextInput, FieldFlowInputProps>(functio
211
253
  disabled={!rightIconPress}
212
254
  style={[styles.iconSlot, styles.iconSlotRight]}
213
255
  hitSlop={8}
256
+ accessibilityRole={rightIconPress ? 'button' : undefined}
214
257
  >
215
- {renderIconNode(resolvedRightIcon, iconColor)}
258
+ {renderFieldFlowIcon(resolvedRightIcon, iconColor, iconSize)}
216
259
  </Pressable>
217
260
  ) : null}
218
261
  </View>
@@ -221,11 +264,11 @@ export const FieldFlowInput = forwardRef<TextInput, FieldFlowInputProps>(functio
221
264
  <View style={styles.footerRow}>
222
265
  <View style={{ flex: 1 }}>
223
266
  {resolvedError ? (
224
- <Text style={styles.errorText}>{resolvedError}</Text>
267
+ <Text style={[styles.errorText, errorStyle, helperStyle]}>{resolvedError}</Text>
225
268
  ) : successText ? (
226
- <Text style={styles.successText}>{successText}</Text>
269
+ <Text style={[styles.successText, helperStyle]}>{successText}</Text>
227
270
  ) : helperText ? (
228
- <Text style={styles.helperText}>{helperText}</Text>
271
+ <Text style={[styles.helperText, helperStyle]}>{helperText}</Text>
229
272
  ) : null}
230
273
  </View>
231
274
  {showCharacterCount && maxLength ? (
@@ -1,5 +1,5 @@
1
1
  import { StyleSheet } from 'react-native';
2
- import type { FieldFlowState, FieldFlowTheme } from '../../types';
2
+ import type { FieldFlowState, FieldFlowTheme, FieldFlowVariant } from '../../types';
3
3
 
4
4
  export function borderColorFor(theme: FieldFlowTheme, state: FieldFlowState): string {
5
5
  switch (state) {
@@ -24,7 +24,17 @@ export function iconColorFor(theme: FieldFlowTheme, state: FieldFlowState): stri
24
24
  return state === 'focused' ? theme.colors.iconFocused : theme.colors.icon;
25
25
  }
26
26
 
27
- export function createStyles(theme: FieldFlowTheme, state: FieldFlowState, multiline: boolean) {
27
+ export function createStyles(
28
+ theme: FieldFlowTheme,
29
+ state: FieldFlowState,
30
+ multiline: boolean,
31
+ variant: FieldFlowVariant = 'outlined',
32
+ ) {
33
+ const isUnderline = variant === 'underline';
34
+ const isFilled = variant === 'filled';
35
+ const isFloating = variant === 'floating';
36
+ const borderW = state === 'focused' ? theme.spacing.borderWidthFocused : theme.spacing.borderWidth;
37
+
28
38
  return StyleSheet.create({
29
39
  wrapper: {
30
40
  marginBottom: theme.spacing.gap * 2,
@@ -34,17 +44,36 @@ export function createStyles(theme: FieldFlowTheme, state: FieldFlowState, multi
34
44
  marginBottom: theme.spacing.gap / 1.5,
35
45
  color: labelColorFor(theme, state),
36
46
  fontWeight: '500',
47
+ fontFamily: theme.typography.fontFamily,
37
48
  },
38
49
  inputContainer: {
39
50
  flexDirection: 'row',
40
51
  alignItems: multiline ? 'flex-start' : 'center',
41
- backgroundColor: state === 'disabled' ? theme.colors.backgroundDisabled : theme.colors.background,
42
- borderRadius: theme.spacing.borderRadius,
43
- borderWidth: state === 'focused' ? theme.spacing.borderWidthFocused : theme.spacing.borderWidth,
52
+ backgroundColor: state === 'disabled'
53
+ ? theme.colors.backgroundDisabled
54
+ : isFilled
55
+ ? theme.colors.backgroundDisabled
56
+ : isUnderline
57
+ ? 'transparent'
58
+ : theme.colors.background,
59
+ borderRadius: isUnderline ? 0 : theme.spacing.borderRadius,
60
+ borderWidth: isUnderline ? 0 : isFilled ? 0 : borderW,
61
+ borderBottomWidth: isUnderline ? borderW + (state === 'focused' ? 0.5 : 0) : isFilled ? 0 : borderW,
44
62
  borderColor: borderColorFor(theme, state),
45
63
  paddingHorizontal: theme.spacing.paddingHorizontal,
46
64
  paddingVertical: multiline ? theme.spacing.paddingVertical : 0,
47
- minHeight: multiline ? undefined : theme.spacing.paddingVertical * 2 + theme.typography.fontSize + 6,
65
+ minHeight: multiline
66
+ ? undefined
67
+ : theme.spacing.paddingVertical * 2 + theme.typography.fontSize + 6 + (isFloating ? 6 : 0),
68
+ paddingTop: isFloating && !multiline ? theme.spacing.paddingVertical + 6 : undefined,
69
+ },
70
+ floatingLabel: {
71
+ position: 'absolute',
72
+ left: theme.spacing.paddingHorizontal,
73
+ backgroundColor: isFilled ? theme.colors.backgroundDisabled : theme.colors.background,
74
+ paddingHorizontal: 4,
75
+ zIndex: 2,
76
+ fontFamily: theme.typography.fontFamily,
48
77
  },
49
78
  input: {
50
79
  flex: 1,
@@ -53,6 +82,8 @@ export function createStyles(theme: FieldFlowTheme, state: FieldFlowState, multi
53
82
  paddingVertical: multiline ? 0 : theme.spacing.paddingVertical,
54
83
  textAlignVertical: multiline ? 'top' : 'center',
55
84
  minHeight: multiline ? 80 : undefined,
85
+ fontFamily: theme.typography.fontFamily,
86
+ fontWeight: theme.typography.fontWeight,
56
87
  },
57
88
  iconSlot: {
58
89
  alignItems: 'center',
@@ -74,21 +105,25 @@ export function createStyles(theme: FieldFlowTheme, state: FieldFlowState, multi
74
105
  fontSize: theme.typography.helperFontSize,
75
106
  color: theme.colors.label,
76
107
  flexShrink: 1,
108
+ fontFamily: theme.typography.fontFamily,
77
109
  },
78
110
  errorText: {
79
111
  fontSize: theme.typography.helperFontSize,
80
112
  color: theme.colors.error,
81
113
  flexShrink: 1,
114
+ fontFamily: theme.typography.fontFamily,
82
115
  },
83
116
  successText: {
84
117
  fontSize: theme.typography.helperFontSize,
85
118
  color: theme.colors.success,
86
119
  flexShrink: 1,
120
+ fontFamily: theme.typography.fontFamily,
87
121
  },
88
122
  charCount: {
89
123
  fontSize: theme.typography.helperFontSize,
90
124
  color: theme.colors.placeholder,
91
125
  marginLeft: theme.spacing.gap,
126
+ fontFamily: theme.typography.fontFamily,
92
127
  },
93
128
  });
94
129
  }
@@ -1,12 +1,19 @@
1
- import type React from 'react';
2
1
  import type { TextInputProps, ViewStyle, TextStyle } from 'react-native';
3
- import type { FieldFlowFieldType, FieldFlowRules, DeepPartial, FieldFlowTheme } from '../../types';
4
- import type { FieldFlowIconName } from '../../icons';
2
+ import type { FieldFlowFieldType, FieldFlowRules, FieldFlowVariant, DeepPartial, FieldFlowTheme } from '../../types';
3
+ import type { FieldFlowIconSource } from '../../icons';
5
4
 
6
5
  export interface FieldFlowInputProps
7
6
  extends Omit<TextInputProps, 'style' | 'onChangeText' | 'onFocus' | 'onBlur' | 'onSubmitEditing'> {
8
7
  /** Determines keyboard, secure entry, validation defaults, and default icon behavior. */
9
8
  type?: FieldFlowFieldType;
9
+ /**
10
+ * Visual style.
11
+ * - `outlined` (default) — bordered box
12
+ * - `filled` — filled background, no/soft border
13
+ * - `underline` — bottom border only
14
+ * - `floating` — label animates into the border when focused/filled
15
+ */
16
+ variant?: FieldFlowVariant;
10
17
  label?: string;
11
18
  helperText?: string;
12
19
  errorText?: string;
@@ -32,10 +39,20 @@ export interface FieldFlowInputProps
32
39
  /** Called with the validation result whenever a validation pass runs. */
33
40
  onValidate?: (result: { valid: boolean; message?: string }) => void;
34
41
 
35
- leftIcon?: FieldFlowIconName | React.ReactNode;
36
- rightIcon?: FieldFlowIconName | React.ReactNode;
42
+ /**
43
+ * Built-in icon name or any custom React node.
44
+ * Pass `null` to hide the type’s default icon (e.g. mail for email).
45
+ */
46
+ leftIcon?: FieldFlowIconSource | null;
47
+ /**
48
+ * Built-in icon name or any custom React node.
49
+ * Pass `null` to suppress automatic right icons (clear / password toggle / status).
50
+ */
51
+ rightIcon?: FieldFlowIconSource | null;
37
52
  onLeftIconPress?: () => void;
38
53
  onRightIconPress?: () => void;
54
+ /** Override default icon size. Default: 18. */
55
+ iconSize?: number;
39
56
  /** Shows an "x" clear button when there's text (defaults to true for `search`, false otherwise). */
40
57
  clearable?: boolean;
41
58
 
@@ -46,9 +63,11 @@ export interface FieldFlowInputProps
46
63
  onSubmitEditing?: () => void;
47
64
 
48
65
  containerStyle?: ViewStyle;
66
+ inputContainerStyle?: ViewStyle;
49
67
  inputStyle?: TextStyle;
50
68
  labelStyle?: TextStyle;
51
69
  helperStyle?: TextStyle;
70
+ errorStyle?: TextStyle;
52
71
  theme?: DeepPartial<FieldFlowTheme>;
53
72
 
54
73
  testID?: string;
@@ -0,0 +1,76 @@
1
+ import React, { forwardRef, useCallback, useMemo, useState } from 'react';
2
+ import type { TextInput } from 'react-native';
3
+ import { FieldFlowInput } from '../FieldFlowInput';
4
+ import { applyMask } from '../../utils/masks';
5
+ import type { FieldFlowIconSource } from '../../icons';
6
+ import type { FieldFlowMaskedInputProps } from './types';
7
+
8
+ function keyboardForMask(mask: FieldFlowMaskedInputProps['mask']) {
9
+ switch (mask) {
10
+ case 'currency':
11
+ return 'decimal-pad' as const;
12
+ case 'phone':
13
+ return 'phone-pad' as const;
14
+ default:
15
+ return 'number-pad' as const;
16
+ }
17
+ }
18
+
19
+ const DEFAULT_MASK_ICONS: Record<FieldFlowMaskedInputProps['mask'], FieldFlowIconSource> = {
20
+ phone: 'phone',
21
+ card: 'creditCard',
22
+ currency: 'currency',
23
+ date: 'calendar',
24
+ custom: 'hash',
25
+ };
26
+
27
+ export const FieldFlowMaskedInput = forwardRef<TextInput, FieldFlowMaskedInputProps>(
28
+ function FieldFlowMaskedInput(
29
+ {
30
+ mask,
31
+ maskOptions,
32
+ value,
33
+ defaultValue,
34
+ onChangeText,
35
+ valueMode = 'formatted',
36
+ leftIcon,
37
+ ...rest
38
+ },
39
+ ref,
40
+ ) {
41
+ const isControlled = value !== undefined;
42
+ const [internal, setInternal] = useState(() => {
43
+ if (defaultValue === undefined) return '';
44
+ return applyMask(defaultValue, mask, maskOptions).formatted;
45
+ });
46
+
47
+ const displayValue = useMemo(() => {
48
+ const source = isControlled ? value ?? '' : internal;
49
+ return applyMask(source, mask, maskOptions).formatted;
50
+ }, [isControlled, value, internal, mask, maskOptions]);
51
+
52
+ const handleChange = useCallback(
53
+ (rawInput: string) => {
54
+ const result = applyMask(rawInput, mask, maskOptions);
55
+ if (!isControlled) setInternal(result.formatted);
56
+ const primary = valueMode === 'raw' ? result.raw : result.formatted;
57
+ onChangeText?.(primary, result.raw);
58
+ },
59
+ [isControlled, mask, maskOptions, onChangeText, valueMode],
60
+ );
61
+
62
+ return (
63
+ <FieldFlowInput
64
+ ref={ref}
65
+ {...rest}
66
+ type="text"
67
+ value={displayValue}
68
+ onChangeText={handleChange}
69
+ keyboardType={keyboardForMask(mask)}
70
+ autoCapitalize="none"
71
+ autoCorrect={false}
72
+ leftIcon={leftIcon !== undefined ? leftIcon : DEFAULT_MASK_ICONS[mask]}
73
+ />
74
+ );
75
+ },
76
+ );
@@ -0,0 +1,2 @@
1
+ export { FieldFlowMaskedInput } from './FieldFlowMaskedInput';
2
+ export type { FieldFlowMaskedInputProps } from './types';
@@ -0,0 +1,22 @@
1
+ import type { FieldFlowInputProps } from '../FieldFlowInput/types';
2
+ import type { ApplyMaskOptions, FieldFlowMaskType } from '../../utils/masks';
3
+
4
+ export interface FieldFlowMaskedInputProps
5
+ extends Omit<FieldFlowInputProps, 'type' | 'onChangeText' | 'value' | 'defaultValue'> {
6
+ /** Built-in mask. Use `custom` with `maskOptions.pattern` for your own pattern (`#` = digit). */
7
+ mask: FieldFlowMaskType;
8
+ maskOptions?: ApplyMaskOptions;
9
+
10
+ value?: string;
11
+ defaultValue?: string;
12
+ /**
13
+ * Called with both the formatted display value and the raw unmasked value.
14
+ * Prefer `raw` when posting to an API.
15
+ */
16
+ onChangeText?: (formatted: string, raw: string) => void;
17
+ /**
18
+ * Which value to keep in controlled `value` / emit as the primary string.
19
+ * Default: `'formatted'`.
20
+ */
21
+ valueMode?: 'formatted' | 'raw';
22
+ }