rn-inputforge 0.3.1 → 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.
- package/README.md +224 -19
- package/lib/commonjs/components/FieldFlowCheckbox/FieldFlowCheckbox.js +131 -0
- package/lib/commonjs/components/FieldFlowCheckbox/FieldFlowCheckbox.js.map +1 -0
- package/lib/commonjs/components/FieldFlowCheckbox/index.js +13 -0
- package/lib/commonjs/components/FieldFlowCheckbox/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.js +536 -0
- package/lib/commonjs/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.js.map +1 -0
- package/lib/commonjs/components/FieldFlowDateTimePicker/index.js +13 -0
- package/lib/commonjs/components/FieldFlowDateTimePicker/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowDateTimePicker/types.js +6 -0
- package/lib/commonjs/components/FieldFlowDateTimePicker/types.js.map +1 -0
- package/lib/commonjs/components/FieldFlowDropdown/FieldFlowDropdown.js +24 -9
- package/lib/commonjs/components/FieldFlowDropdown/FieldFlowDropdown.js.map +1 -1
- package/lib/commonjs/components/FieldFlowInput/FieldFlowInput.js +55 -24
- package/lib/commonjs/components/FieldFlowInput/FieldFlowInput.js.map +1 -1
- package/lib/commonjs/components/FieldFlowInput/styles.js +32 -11
- package/lib/commonjs/components/FieldFlowInput/styles.js.map +1 -1
- package/lib/commonjs/components/FieldFlowMaskedInput/FieldFlowMaskedInput.js +66 -0
- package/lib/commonjs/components/FieldFlowMaskedInput/FieldFlowMaskedInput.js.map +1 -0
- package/lib/commonjs/components/FieldFlowMaskedInput/index.js +13 -0
- package/lib/commonjs/components/FieldFlowMaskedInput/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowMaskedInput/types.js +6 -0
- package/lib/commonjs/components/FieldFlowMaskedInput/types.js.map +1 -0
- package/lib/commonjs/components/FieldFlowOTP/FieldFlowOTP.js +255 -0
- package/lib/commonjs/components/FieldFlowOTP/FieldFlowOTP.js.map +1 -0
- package/lib/commonjs/components/FieldFlowOTP/index.js +13 -0
- package/lib/commonjs/components/FieldFlowOTP/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowOTP/types.js +6 -0
- package/lib/commonjs/components/FieldFlowOTP/types.js.map +1 -0
- package/lib/commonjs/components/FieldFlowRadio/FieldFlowRadio.js +164 -0
- package/lib/commonjs/components/FieldFlowRadio/FieldFlowRadio.js.map +1 -0
- package/lib/commonjs/components/FieldFlowRadio/index.js +13 -0
- package/lib/commonjs/components/FieldFlowRadio/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowSwitch/FieldFlowSwitch.js +152 -0
- package/lib/commonjs/components/FieldFlowSwitch/FieldFlowSwitch.js.map +1 -0
- package/lib/commonjs/components/FieldFlowSwitch/index.js +13 -0
- package/lib/commonjs/components/FieldFlowSwitch/index.js.map +1 -0
- package/lib/commonjs/hooks/index.js.map +1 -1
- package/lib/commonjs/hooks/useFieldFlowForm.js +146 -31
- package/lib/commonjs/hooks/useFieldFlowForm.js.map +1 -1
- package/lib/commonjs/icons/Icon.js +719 -30
- package/lib/commonjs/icons/Icon.js.map +1 -1
- package/lib/commonjs/icons/index.js +7 -0
- package/lib/commonjs/icons/index.js.map +1 -1
- package/lib/commonjs/icons/renderIcon.js +24 -0
- package/lib/commonjs/icons/renderIcon.js.map +1 -0
- package/lib/commonjs/index.js +127 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/utils/masks.js +130 -0
- package/lib/commonjs/utils/masks.js.map +1 -0
- package/lib/commonjs/validation/index.js +25 -0
- package/lib/commonjs/validation/index.js.map +1 -1
- package/lib/commonjs/validation/zodAdapter.js +91 -0
- package/lib/commonjs/validation/zodAdapter.js.map +1 -0
- package/lib/module/components/FieldFlowCheckbox/FieldFlowCheckbox.js +126 -0
- package/lib/module/components/FieldFlowCheckbox/FieldFlowCheckbox.js.map +1 -0
- package/lib/module/components/FieldFlowCheckbox/index.js +4 -0
- package/lib/module/components/FieldFlowCheckbox/index.js.map +1 -0
- package/lib/module/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.js +531 -0
- package/lib/module/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.js.map +1 -0
- package/lib/module/components/FieldFlowDateTimePicker/index.js +4 -0
- package/lib/module/components/FieldFlowDateTimePicker/index.js.map +1 -0
- package/lib/module/components/FieldFlowDateTimePicker/types.js +4 -0
- package/lib/module/components/FieldFlowDateTimePicker/types.js.map +1 -0
- package/lib/module/components/FieldFlowDropdown/FieldFlowDropdown.js +24 -9
- package/lib/module/components/FieldFlowDropdown/FieldFlowDropdown.js.map +1 -1
- package/lib/module/components/FieldFlowInput/FieldFlowInput.js +59 -28
- package/lib/module/components/FieldFlowInput/FieldFlowInput.js.map +1 -1
- package/lib/module/components/FieldFlowInput/styles.js +32 -11
- package/lib/module/components/FieldFlowInput/styles.js.map +1 -1
- package/lib/module/components/FieldFlowMaskedInput/FieldFlowMaskedInput.js +61 -0
- package/lib/module/components/FieldFlowMaskedInput/FieldFlowMaskedInput.js.map +1 -0
- package/lib/module/components/FieldFlowMaskedInput/index.js +4 -0
- package/lib/module/components/FieldFlowMaskedInput/index.js.map +1 -0
- package/lib/module/components/FieldFlowMaskedInput/types.js +4 -0
- package/lib/module/components/FieldFlowMaskedInput/types.js.map +1 -0
- package/lib/module/components/FieldFlowOTP/FieldFlowOTP.js +250 -0
- package/lib/module/components/FieldFlowOTP/FieldFlowOTP.js.map +1 -0
- package/lib/module/components/FieldFlowOTP/index.js +4 -0
- package/lib/module/components/FieldFlowOTP/index.js.map +1 -0
- package/lib/module/components/FieldFlowOTP/types.js +4 -0
- package/lib/module/components/FieldFlowOTP/types.js.map +1 -0
- package/lib/module/components/FieldFlowRadio/FieldFlowRadio.js +159 -0
- package/lib/module/components/FieldFlowRadio/FieldFlowRadio.js.map +1 -0
- package/lib/module/components/FieldFlowRadio/index.js +4 -0
- package/lib/module/components/FieldFlowRadio/index.js.map +1 -0
- package/lib/module/components/FieldFlowSwitch/FieldFlowSwitch.js +147 -0
- package/lib/module/components/FieldFlowSwitch/FieldFlowSwitch.js.map +1 -0
- package/lib/module/components/FieldFlowSwitch/index.js +4 -0
- package/lib/module/components/FieldFlowSwitch/index.js.map +1 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/useFieldFlowForm.js +147 -32
- package/lib/module/hooks/useFieldFlowForm.js.map +1 -1
- package/lib/module/icons/Icon.js +720 -31
- package/lib/module/icons/Icon.js.map +1 -1
- package/lib/module/icons/index.js +1 -0
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/icons/renderIcon.js +19 -0
- package/lib/module/icons/renderIcon.js.map +1 -0
- package/lib/module/index.js +11 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/masks.js +118 -0
- package/lib/module/utils/masks.js.map +1 -0
- package/lib/module/validation/index.js +1 -0
- package/lib/module/validation/index.js.map +1 -1
- package/lib/module/validation/zodAdapter.js +84 -0
- package/lib/module/validation/zodAdapter.js.map +1 -0
- package/lib/typescript/components/FieldFlowCheckbox/FieldFlowCheckbox.d.ts +36 -0
- package/lib/typescript/components/FieldFlowCheckbox/FieldFlowCheckbox.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowCheckbox/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowCheckbox/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.d.ts +4 -0
- package/lib/typescript/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowDateTimePicker/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowDateTimePicker/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowDateTimePicker/types.d.ts +55 -0
- package/lib/typescript/components/FieldFlowDateTimePicker/types.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowDropdown/FieldFlowDropdown.d.ts.map +1 -1
- package/lib/typescript/components/FieldFlowDropdown/types.d.ts +7 -1
- package/lib/typescript/components/FieldFlowDropdown/types.d.ts.map +1 -1
- package/lib/typescript/components/FieldFlowInput/FieldFlowInput.d.ts.map +1 -1
- package/lib/typescript/components/FieldFlowInput/styles.d.ts +19 -2
- package/lib/typescript/components/FieldFlowInput/styles.d.ts.map +1 -1
- package/lib/typescript/components/FieldFlowInput/types.d.ts +24 -5
- package/lib/typescript/components/FieldFlowInput/types.d.ts.map +1 -1
- package/lib/typescript/components/FieldFlowMaskedInput/FieldFlowMaskedInput.d.ts +5 -0
- package/lib/typescript/components/FieldFlowMaskedInput/FieldFlowMaskedInput.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowMaskedInput/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowMaskedInput/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowMaskedInput/types.d.ts +20 -0
- package/lib/typescript/components/FieldFlowMaskedInput/types.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowOTP/FieldFlowOTP.d.ts +4 -0
- package/lib/typescript/components/FieldFlowOTP/FieldFlowOTP.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowOTP/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowOTP/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowOTP/types.d.ts +52 -0
- package/lib/typescript/components/FieldFlowOTP/types.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowRadio/FieldFlowRadio.d.ts +39 -0
- package/lib/typescript/components/FieldFlowRadio/FieldFlowRadio.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowRadio/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowRadio/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowSwitch/FieldFlowSwitch.d.ts +31 -0
- package/lib/typescript/components/FieldFlowSwitch/FieldFlowSwitch.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowSwitch/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowSwitch/index.d.ts.map +1 -0
- package/lib/typescript/hooks/index.d.ts +1 -1
- package/lib/typescript/hooks/index.d.ts.map +1 -1
- package/lib/typescript/hooks/useFieldFlowForm.d.ts +46 -7
- package/lib/typescript/hooks/useFieldFlowForm.d.ts.map +1 -1
- package/lib/typescript/icons/Icon.d.ts +2 -2
- package/lib/typescript/icons/Icon.d.ts.map +1 -1
- package/lib/typescript/icons/index.d.ts +2 -0
- package/lib/typescript/icons/index.d.ts.map +1 -1
- package/lib/typescript/icons/renderIcon.d.ts +6 -0
- package/lib/typescript/icons/renderIcon.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +20 -6
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/types/index.d.ts +4 -0
- package/lib/typescript/types/index.d.ts.map +1 -1
- package/lib/typescript/utils/masks.d.ts +37 -0
- package/lib/typescript/utils/masks.d.ts.map +1 -0
- package/lib/typescript/validation/index.d.ts +2 -0
- package/lib/typescript/validation/index.d.ts.map +1 -1
- package/lib/typescript/validation/zodAdapter.d.ts +53 -0
- package/lib/typescript/validation/zodAdapter.d.ts.map +1 -0
- package/package.json +17 -3
- package/src/components/FieldFlowCheckbox/FieldFlowCheckbox.tsx +174 -0
- package/src/components/FieldFlowCheckbox/index.ts +2 -0
- package/src/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.tsx +585 -0
- package/src/components/FieldFlowDateTimePicker/index.ts +2 -0
- package/src/components/FieldFlowDateTimePicker/types.ts +57 -0
- package/src/components/FieldFlowDropdown/FieldFlowDropdown.tsx +17 -7
- package/src/components/FieldFlowDropdown/types.ts +8 -1
- package/src/components/FieldFlowInput/FieldFlowInput.tsx +69 -26
- package/src/components/FieldFlowInput/styles.ts +41 -6
- package/src/components/FieldFlowInput/types.ts +24 -5
- package/src/components/FieldFlowMaskedInput/FieldFlowMaskedInput.tsx +76 -0
- package/src/components/FieldFlowMaskedInput/index.ts +2 -0
- package/src/components/FieldFlowMaskedInput/types.ts +22 -0
- package/src/components/FieldFlowOTP/FieldFlowOTP.tsx +305 -0
- package/src/components/FieldFlowOTP/index.ts +2 -0
- package/src/components/FieldFlowOTP/types.ts +57 -0
- package/src/components/FieldFlowRadio/FieldFlowRadio.tsx +215 -0
- package/src/components/FieldFlowRadio/index.ts +2 -0
- package/src/components/FieldFlowSwitch/FieldFlowSwitch.tsx +172 -0
- package/src/components/FieldFlowSwitch/index.ts +2 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useFieldFlowForm.ts +209 -33
- package/src/icons/Icon.tsx +451 -5
- package/src/icons/index.ts +2 -0
- package/src/icons/renderIcon.tsx +17 -0
- package/src/index.ts +47 -4
- package/src/types/index.ts +5 -0
- package/src/utils/masks.ts +137 -0
- package/src/validation/index.ts +7 -0
- 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 {
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
148
|
-
let resolvedRightIcon =
|
|
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 (
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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(
|
|
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'
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
36
|
-
|
|
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,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
|
+
}
|