rn-inputforge 0.5.0 → 0.5.1
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 +236 -0
- package/lib/commonjs/a11y/audit.js +72 -0
- package/lib/commonjs/a11y/audit.js.map +1 -0
- package/lib/commonjs/a11y/index.js +13 -0
- package/lib/commonjs/a11y/index.js.map +1 -0
- package/lib/commonjs/autofill/index.js +25 -0
- package/lib/commonjs/autofill/index.js.map +1 -0
- package/lib/commonjs/autofill/profiles.js +199 -0
- package/lib/commonjs/autofill/profiles.js.map +1 -0
- package/lib/commonjs/components/FieldFlowA11yAudit/FieldFlowA11yAudit.js +99 -0
- package/lib/commonjs/components/FieldFlowA11yAudit/FieldFlowA11yAudit.js.map +1 -0
- package/lib/commonjs/components/FieldFlowA11yAudit/index.js +13 -0
- package/lib/commonjs/components/FieldFlowA11yAudit/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowAuthActions/FieldFlowAuthActions.js +130 -0
- package/lib/commonjs/components/FieldFlowAuthActions/FieldFlowAuthActions.js.map +1 -0
- package/lib/commonjs/components/FieldFlowAuthActions/index.js +13 -0
- package/lib/commonjs/components/FieldFlowAuthActions/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowCompleteness/FieldFlowCompleteness.js +100 -0
- package/lib/commonjs/components/FieldFlowCompleteness/FieldFlowCompleteness.js.map +1 -0
- package/lib/commonjs/components/FieldFlowCompleteness/index.js +13 -0
- package/lib/commonjs/components/FieldFlowCompleteness/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowFormHeader/FieldFlowFormHeader.js +102 -0
- package/lib/commonjs/components/FieldFlowFormHeader/FieldFlowFormHeader.js.map +1 -0
- package/lib/commonjs/components/FieldFlowFormHeader/index.js +13 -0
- package/lib/commonjs/components/FieldFlowFormHeader/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowInfoTip/FieldFlowInfoTip.js +121 -0
- package/lib/commonjs/components/FieldFlowInfoTip/FieldFlowInfoTip.js.map +1 -0
- package/lib/commonjs/components/FieldFlowInfoTip/index.js +13 -0
- package/lib/commonjs/components/FieldFlowInfoTip/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowInlineAlert/FieldFlowInlineAlert.js +142 -0
- package/lib/commonjs/components/FieldFlowInlineAlert/FieldFlowInlineAlert.js.map +1 -0
- package/lib/commonjs/components/FieldFlowInlineAlert/index.js +13 -0
- package/lib/commonjs/components/FieldFlowInlineAlert/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowInput/FieldFlowInput.js +44 -18
- package/lib/commonjs/components/FieldFlowInput/FieldFlowInput.js.map +1 -1
- package/lib/commonjs/components/FieldFlowInput/styles.js +49 -13
- package/lib/commonjs/components/FieldFlowInput/styles.js.map +1 -1
- package/lib/commonjs/components/FieldFlowKeyboardToolbar/FieldFlowKeyboardToolbar.js +137 -0
- package/lib/commonjs/components/FieldFlowKeyboardToolbar/FieldFlowKeyboardToolbar.js.map +1 -0
- package/lib/commonjs/components/FieldFlowKeyboardToolbar/index.js +19 -0
- package/lib/commonjs/components/FieldFlowKeyboardToolbar/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowSkeleton/FieldFlowSkeleton.js +101 -0
- package/lib/commonjs/components/FieldFlowSkeleton/FieldFlowSkeleton.js.map +1 -0
- package/lib/commonjs/components/FieldFlowSkeleton/index.js +13 -0
- package/lib/commonjs/components/FieldFlowSkeleton/index.js.map +1 -0
- package/lib/commonjs/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.js +6 -4
- package/lib/commonjs/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.js.map +1 -1
- package/lib/commonjs/hooks/index.js +21 -0
- package/lib/commonjs/hooks/index.js.map +1 -1
- package/lib/commonjs/hooks/useFieldFlowCompleteness.js +46 -0
- package/lib/commonjs/hooks/useFieldFlowCompleteness.js.map +1 -0
- package/lib/commonjs/hooks/useFieldFlowSubmitQueue.js +145 -0
- package/lib/commonjs/hooks/useFieldFlowSubmitQueue.js.map +1 -0
- package/lib/commonjs/hooks/useKeyboardManager.js +15 -0
- package/lib/commonjs/hooks/useKeyboardManager.js.map +1 -1
- package/lib/commonjs/hooks/useSmartPaste.js +27 -0
- package/lib/commonjs/hooks/useSmartPaste.js.map +1 -0
- package/lib/commonjs/index.js +144 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/paste/index.js +19 -0
- package/lib/commonjs/paste/index.js.map +1 -0
- package/lib/commonjs/paste/smartPaste.js +113 -0
- package/lib/commonjs/paste/smartPaste.js.map +1 -0
- package/lib/commonjs/schema/FieldFlowSchemaForm.js +219 -0
- package/lib/commonjs/schema/FieldFlowSchemaForm.js.map +1 -0
- package/lib/commonjs/schema/index.js +13 -0
- package/lib/commonjs/schema/index.js.map +1 -0
- package/lib/commonjs/schema/types.js +6 -0
- package/lib/commonjs/schema/types.js.map +1 -0
- package/lib/commonjs/theme/ThemeProvider.js +65 -4
- package/lib/commonjs/theme/ThemeProvider.js.map +1 -1
- package/lib/commonjs/theme/appearance.js +93 -0
- package/lib/commonjs/theme/appearance.js.map +1 -0
- package/lib/commonjs/theme/defaultTheme.js +17 -2
- package/lib/commonjs/theme/defaultTheme.js.map +1 -1
- package/lib/commonjs/theme/index.js +19 -0
- package/lib/commonjs/theme/index.js.map +1 -1
- package/lib/commonjs/types/globals.d.js +2 -0
- package/lib/commonjs/types/globals.d.js.map +1 -0
- package/lib/module/a11y/audit.js +68 -0
- package/lib/module/a11y/audit.js.map +1 -0
- package/lib/module/a11y/index.js +4 -0
- package/lib/module/a11y/index.js.map +1 -0
- package/lib/module/autofill/index.js +4 -0
- package/lib/module/autofill/index.js.map +1 -0
- package/lib/module/autofill/profiles.js +193 -0
- package/lib/module/autofill/profiles.js.map +1 -0
- package/lib/module/components/FieldFlowA11yAudit/FieldFlowA11yAudit.js +94 -0
- package/lib/module/components/FieldFlowA11yAudit/FieldFlowA11yAudit.js.map +1 -0
- package/lib/module/components/FieldFlowA11yAudit/index.js +4 -0
- package/lib/module/components/FieldFlowA11yAudit/index.js.map +1 -0
- package/lib/module/components/FieldFlowAuthActions/FieldFlowAuthActions.js +125 -0
- package/lib/module/components/FieldFlowAuthActions/FieldFlowAuthActions.js.map +1 -0
- package/lib/module/components/FieldFlowAuthActions/index.js +4 -0
- package/lib/module/components/FieldFlowAuthActions/index.js.map +1 -0
- package/lib/module/components/FieldFlowCompleteness/FieldFlowCompleteness.js +95 -0
- package/lib/module/components/FieldFlowCompleteness/FieldFlowCompleteness.js.map +1 -0
- package/lib/module/components/FieldFlowCompleteness/index.js +4 -0
- package/lib/module/components/FieldFlowCompleteness/index.js.map +1 -0
- package/lib/module/components/FieldFlowFormHeader/FieldFlowFormHeader.js +97 -0
- package/lib/module/components/FieldFlowFormHeader/FieldFlowFormHeader.js.map +1 -0
- package/lib/module/components/FieldFlowFormHeader/index.js +4 -0
- package/lib/module/components/FieldFlowFormHeader/index.js.map +1 -0
- package/lib/module/components/FieldFlowInfoTip/FieldFlowInfoTip.js +116 -0
- package/lib/module/components/FieldFlowInfoTip/FieldFlowInfoTip.js.map +1 -0
- package/lib/module/components/FieldFlowInfoTip/index.js +4 -0
- package/lib/module/components/FieldFlowInfoTip/index.js.map +1 -0
- package/lib/module/components/FieldFlowInlineAlert/FieldFlowInlineAlert.js +137 -0
- package/lib/module/components/FieldFlowInlineAlert/FieldFlowInlineAlert.js.map +1 -0
- package/lib/module/components/FieldFlowInlineAlert/index.js +4 -0
- package/lib/module/components/FieldFlowInlineAlert/index.js.map +1 -0
- package/lib/module/components/FieldFlowInput/FieldFlowInput.js +44 -18
- package/lib/module/components/FieldFlowInput/FieldFlowInput.js.map +1 -1
- package/lib/module/components/FieldFlowInput/styles.js +49 -13
- package/lib/module/components/FieldFlowInput/styles.js.map +1 -1
- package/lib/module/components/FieldFlowKeyboardToolbar/FieldFlowKeyboardToolbar.js +131 -0
- package/lib/module/components/FieldFlowKeyboardToolbar/FieldFlowKeyboardToolbar.js.map +1 -0
- package/lib/module/components/FieldFlowKeyboardToolbar/index.js +4 -0
- package/lib/module/components/FieldFlowKeyboardToolbar/index.js.map +1 -0
- package/lib/module/components/FieldFlowSkeleton/FieldFlowSkeleton.js +96 -0
- package/lib/module/components/FieldFlowSkeleton/FieldFlowSkeleton.js.map +1 -0
- package/lib/module/components/FieldFlowSkeleton/index.js +4 -0
- package/lib/module/components/FieldFlowSkeleton/index.js.map +1 -0
- package/lib/module/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.js +5 -3
- package/lib/module/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.js.map +1 -1
- package/lib/module/hooks/index.js +3 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/useFieldFlowCompleteness.js +42 -0
- package/lib/module/hooks/useFieldFlowCompleteness.js.map +1 -0
- package/lib/module/hooks/useFieldFlowSubmitQueue.js +141 -0
- package/lib/module/hooks/useFieldFlowSubmitQueue.js.map +1 -0
- package/lib/module/hooks/useKeyboardManager.js +15 -0
- package/lib/module/hooks/useKeyboardManager.js.map +1 -1
- package/lib/module/hooks/useSmartPaste.js +23 -0
- package/lib/module/hooks/useSmartPaste.js.map +1 -0
- package/lib/module/index.js +17 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/paste/index.js +4 -0
- package/lib/module/paste/index.js.map +1 -0
- package/lib/module/paste/smartPaste.js +108 -0
- package/lib/module/paste/smartPaste.js.map +1 -0
- package/lib/module/schema/FieldFlowSchemaForm.js +214 -0
- package/lib/module/schema/FieldFlowSchemaForm.js.map +1 -0
- package/lib/module/schema/index.js +4 -0
- package/lib/module/schema/index.js.map +1 -0
- package/lib/module/schema/types.js +4 -0
- package/lib/module/schema/types.js.map +1 -0
- package/lib/module/theme/ThemeProvider.js +66 -5
- package/lib/module/theme/ThemeProvider.js.map +1 -1
- package/lib/module/theme/appearance.js +87 -0
- package/lib/module/theme/appearance.js.map +1 -0
- package/lib/module/theme/defaultTheme.js +17 -2
- package/lib/module/theme/defaultTheme.js.map +1 -1
- package/lib/module/theme/index.js +1 -0
- package/lib/module/theme/index.js.map +1 -1
- package/lib/module/types/globals.d.js +2 -0
- package/lib/module/types/globals.d.js.map +1 -0
- package/lib/typescript/a11y/audit.d.ts +32 -0
- package/lib/typescript/a11y/audit.d.ts.map +1 -0
- package/lib/typescript/a11y/index.d.ts +3 -0
- package/lib/typescript/a11y/index.d.ts.map +1 -0
- package/lib/typescript/autofill/index.d.ts +3 -0
- package/lib/typescript/autofill/index.d.ts.map +1 -0
- package/lib/typescript/autofill/profiles.d.ts +48 -0
- package/lib/typescript/autofill/profiles.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowA11yAudit/FieldFlowA11yAudit.d.ts +21 -0
- package/lib/typescript/components/FieldFlowA11yAudit/FieldFlowA11yAudit.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowA11yAudit/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowA11yAudit/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowAuthActions/FieldFlowAuthActions.d.ts +29 -0
- package/lib/typescript/components/FieldFlowAuthActions/FieldFlowAuthActions.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowAuthActions/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowAuthActions/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowCompleteness/FieldFlowCompleteness.d.ts +25 -0
- package/lib/typescript/components/FieldFlowCompleteness/FieldFlowCompleteness.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowCompleteness/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowCompleteness/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowFormHeader/FieldFlowFormHeader.d.ts +23 -0
- package/lib/typescript/components/FieldFlowFormHeader/FieldFlowFormHeader.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowFormHeader/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowFormHeader/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowInfoTip/FieldFlowInfoTip.d.ts +19 -0
- package/lib/typescript/components/FieldFlowInfoTip/FieldFlowInfoTip.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowInfoTip/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowInfoTip/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowInlineAlert/FieldFlowInlineAlert.d.ts +22 -0
- package/lib/typescript/components/FieldFlowInlineAlert/FieldFlowInlineAlert.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowInlineAlert/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowInlineAlert/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowInput/FieldFlowInput.d.ts.map +1 -1
- package/lib/typescript/components/FieldFlowInput/styles.d.ts +36 -1
- package/lib/typescript/components/FieldFlowInput/styles.d.ts.map +1 -1
- package/lib/typescript/components/FieldFlowInput/types.d.ts +18 -0
- package/lib/typescript/components/FieldFlowInput/types.d.ts.map +1 -1
- package/lib/typescript/components/FieldFlowKeyboardToolbar/FieldFlowKeyboardToolbar.d.ts +30 -0
- package/lib/typescript/components/FieldFlowKeyboardToolbar/FieldFlowKeyboardToolbar.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowKeyboardToolbar/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowKeyboardToolbar/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowSkeleton/FieldFlowSkeleton.d.ts +21 -0
- package/lib/typescript/components/FieldFlowSkeleton/FieldFlowSkeleton.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowSkeleton/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowSkeleton/index.d.ts.map +1 -0
- package/lib/typescript/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.d.ts +3 -2
- package/lib/typescript/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.d.ts.map +1 -1
- package/lib/typescript/hooks/index.d.ts +6 -0
- package/lib/typescript/hooks/index.d.ts.map +1 -1
- package/lib/typescript/hooks/useFieldFlowCompleteness.d.ts +28 -0
- package/lib/typescript/hooks/useFieldFlowCompleteness.d.ts.map +1 -0
- package/lib/typescript/hooks/useFieldFlowSubmitQueue.d.ts +45 -0
- package/lib/typescript/hooks/useFieldFlowSubmitQueue.d.ts.map +1 -0
- package/lib/typescript/hooks/useKeyboardManager.d.ts +6 -0
- package/lib/typescript/hooks/useKeyboardManager.d.ts.map +1 -1
- package/lib/typescript/hooks/useSmartPaste.d.ts +14 -0
- package/lib/typescript/hooks/useSmartPaste.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +28 -4
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/paste/index.d.ts +3 -0
- package/lib/typescript/paste/index.d.ts.map +1 -0
- package/lib/typescript/paste/smartPaste.d.ts +32 -0
- package/lib/typescript/paste/smartPaste.d.ts.map +1 -0
- package/lib/typescript/schema/FieldFlowSchemaForm.d.ts +28 -0
- package/lib/typescript/schema/FieldFlowSchemaForm.d.ts.map +1 -0
- package/lib/typescript/schema/index.d.ts +4 -0
- package/lib/typescript/schema/index.d.ts.map +1 -0
- package/lib/typescript/schema/types.d.ts +43 -0
- package/lib/typescript/schema/types.d.ts.map +1 -0
- package/lib/typescript/theme/ThemeProvider.d.ts +18 -4
- package/lib/typescript/theme/ThemeProvider.d.ts.map +1 -1
- package/lib/typescript/theme/appearance.d.ts +19 -0
- package/lib/typescript/theme/appearance.d.ts.map +1 -0
- package/lib/typescript/theme/defaultTheme.d.ts.map +1 -1
- package/lib/typescript/theme/index.d.ts +1 -0
- package/lib/typescript/theme/index.d.ts.map +1 -1
- package/lib/typescript/types/index.d.ts +26 -0
- package/lib/typescript/types/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/a11y/audit.ts +99 -0
- package/src/a11y/index.ts +7 -0
- package/src/autofill/index.ts +10 -0
- package/src/autofill/profiles.ts +224 -0
- package/src/components/FieldFlowA11yAudit/FieldFlowA11yAudit.tsx +112 -0
- package/src/components/FieldFlowA11yAudit/index.ts +2 -0
- package/src/components/FieldFlowAuthActions/FieldFlowAuthActions.tsx +146 -0
- package/src/components/FieldFlowAuthActions/index.ts +6 -0
- package/src/components/FieldFlowCompleteness/FieldFlowCompleteness.tsx +109 -0
- package/src/components/FieldFlowCompleteness/index.ts +2 -0
- package/src/components/FieldFlowFormHeader/FieldFlowFormHeader.tsx +106 -0
- package/src/components/FieldFlowFormHeader/index.ts +2 -0
- package/src/components/FieldFlowInfoTip/FieldFlowInfoTip.tsx +120 -0
- package/src/components/FieldFlowInfoTip/index.ts +2 -0
- package/src/components/FieldFlowInlineAlert/FieldFlowInlineAlert.tsx +134 -0
- package/src/components/FieldFlowInlineAlert/index.ts +2 -0
- package/src/components/FieldFlowInput/FieldFlowInput.tsx +71 -13
- package/src/components/FieldFlowInput/styles.ts +58 -16
- package/src/components/FieldFlowInput/types.ts +21 -0
- package/src/components/FieldFlowKeyboardToolbar/FieldFlowKeyboardToolbar.tsx +150 -0
- package/src/components/FieldFlowKeyboardToolbar/index.ts +5 -0
- package/src/components/FieldFlowSkeleton/FieldFlowSkeleton.tsx +105 -0
- package/src/components/FieldFlowSkeleton/index.ts +2 -0
- package/src/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.tsx +8 -4
- package/src/hooks/index.ts +17 -0
- package/src/hooks/useFieldFlowCompleteness.ts +69 -0
- package/src/hooks/useFieldFlowSubmitQueue.ts +206 -0
- package/src/hooks/useKeyboardManager.ts +26 -0
- package/src/hooks/useSmartPaste.ts +33 -0
- package/src/index.ts +90 -0
- package/src/paste/index.ts +7 -0
- package/src/paste/smartPaste.ts +132 -0
- package/src/schema/FieldFlowSchemaForm.tsx +274 -0
- package/src/schema/index.ts +8 -0
- package/src/schema/types.ts +63 -0
- package/src/theme/ThemeProvider.tsx +96 -7
- package/src/theme/appearance.ts +105 -0
- package/src/theme/defaultTheme.ts +11 -0
- package/src/theme/index.ts +1 -0
- package/src/types/globals.d.ts +2 -0
- package/src/types/index.ts +28 -0
|
@@ -10,10 +10,15 @@ import { renderFieldFlowIcon } from '../../icons/renderIcon';
|
|
|
10
10
|
import { useFieldMotion } from '../../motion/useFieldMotion';
|
|
11
11
|
import { FieldFlowAffordance } from '../FieldFlowAffordance';
|
|
12
12
|
import { FieldFlowPasswordStrength } from '../FieldFlowPasswordStrength';
|
|
13
|
+
import { FieldFlowInfoTip } from '../FieldFlowInfoTip';
|
|
13
14
|
import {
|
|
14
15
|
affordanceKindFromFieldType,
|
|
15
16
|
type FieldFlowAffordanceKind,
|
|
16
17
|
} from '../../utils/affordance';
|
|
18
|
+
import {
|
|
19
|
+
autofillRoleFromFieldType,
|
|
20
|
+
getAutofillProps,
|
|
21
|
+
} from '../../autofill/profiles';
|
|
17
22
|
import { createStyles, iconColorFor, labelColorFor } from './styles';
|
|
18
23
|
import type { FieldFlowInputProps } from './types';
|
|
19
24
|
|
|
@@ -58,6 +63,10 @@ export const FieldFlowInput = forwardRef<TextInput, FieldFlowInputProps>(functio
|
|
|
58
63
|
prefix,
|
|
59
64
|
suffix,
|
|
60
65
|
motion = true,
|
|
66
|
+
autofill,
|
|
67
|
+
inputAccessoryViewID,
|
|
68
|
+
helperStatus,
|
|
69
|
+
infoTip,
|
|
61
70
|
maxLength,
|
|
62
71
|
showCharacterCount,
|
|
63
72
|
onSubmitEditing,
|
|
@@ -97,6 +106,11 @@ export const FieldFlowInput = forwardRef<TextInput, FieldFlowInputProps>(functio
|
|
|
97
106
|
[type, required, rules],
|
|
98
107
|
);
|
|
99
108
|
|
|
109
|
+
const autofillProps = useMemo(() => {
|
|
110
|
+
const role = autofill ?? autofillRoleFromFieldType(type);
|
|
111
|
+
return role ? getAutofillProps(role) : undefined;
|
|
112
|
+
}, [autofill, type]);
|
|
113
|
+
|
|
100
114
|
const resolvedError = errorText ?? localError;
|
|
101
115
|
const state = useFieldState({
|
|
102
116
|
isFocused,
|
|
@@ -271,18 +285,56 @@ export const FieldFlowInput = forwardRef<TextInput, FieldFlowInputProps>(functio
|
|
|
271
285
|
return <View style={side === 'left' ? styles.iconSlotLeft : styles.iconSlotRight}>{affix}</View>;
|
|
272
286
|
};
|
|
273
287
|
|
|
288
|
+
const focusRing = theme.appearance?.focusRing ?? 'softGlow';
|
|
289
|
+
const showFocusRing = motion && focusRing !== 'underlinePulse';
|
|
290
|
+
const resolvedHelperStatus =
|
|
291
|
+
helperStatus ??
|
|
292
|
+
(resolvedError ? 'error' : successText ? 'success' : helperText ? 'info' : 'none');
|
|
293
|
+
|
|
294
|
+
const helperIconName =
|
|
295
|
+
resolvedHelperStatus === 'success'
|
|
296
|
+
? 'check'
|
|
297
|
+
: resolvedHelperStatus === 'error' || resolvedHelperStatus === 'warning'
|
|
298
|
+
? 'alert'
|
|
299
|
+
: resolvedHelperStatus === 'info'
|
|
300
|
+
? 'info'
|
|
301
|
+
: null;
|
|
302
|
+
|
|
303
|
+
const helperIconColor =
|
|
304
|
+
resolvedHelperStatus === 'success'
|
|
305
|
+
? theme.colors.success
|
|
306
|
+
: resolvedHelperStatus === 'error'
|
|
307
|
+
? theme.colors.error
|
|
308
|
+
: resolvedHelperStatus === 'warning'
|
|
309
|
+
? theme.colors.warning
|
|
310
|
+
: theme.colors.info;
|
|
311
|
+
|
|
312
|
+
const helperTextStyle =
|
|
313
|
+
resolvedError
|
|
314
|
+
? styles.errorText
|
|
315
|
+
: successText
|
|
316
|
+
? styles.successText
|
|
317
|
+
: resolvedHelperStatus === 'warning'
|
|
318
|
+
? styles.warningText
|
|
319
|
+
: resolvedHelperStatus === 'info' && helperText
|
|
320
|
+
? styles.infoText
|
|
321
|
+
: styles.helperText;
|
|
322
|
+
|
|
274
323
|
return (
|
|
275
324
|
<View style={[styles.wrapper, containerStyle]} testID={testID}>
|
|
276
325
|
{showExternalLabel ? (
|
|
277
|
-
<
|
|
278
|
-
{label}
|
|
279
|
-
|
|
280
|
-
|
|
326
|
+
<View style={styles.labelRow}>
|
|
327
|
+
<Text style={[styles.label, labelStyle]}>
|
|
328
|
+
{label}
|
|
329
|
+
{required ? ' *' : ''}
|
|
330
|
+
</Text>
|
|
331
|
+
{infoTip ? <FieldFlowInfoTip tip={infoTip} theme={themeOverride} /> : null}
|
|
332
|
+
</View>
|
|
281
333
|
) : null}
|
|
282
334
|
|
|
283
335
|
<Animated.View style={fieldMotion.containerStyle}>
|
|
284
336
|
<View style={[styles.inputContainer, inputContainerStyle]}>
|
|
285
|
-
{
|
|
337
|
+
{showFocusRing ? (
|
|
286
338
|
<Animated.View
|
|
287
339
|
pointerEvents="none"
|
|
288
340
|
style={[
|
|
@@ -290,10 +342,9 @@ export const FieldFlowInput = forwardRef<TextInput, FieldFlowInputProps>(functio
|
|
|
290
342
|
{
|
|
291
343
|
opacity: fieldMotion.glowOpacity.interpolate({
|
|
292
344
|
inputRange: [0, 1],
|
|
293
|
-
outputRange: [0, 0.22],
|
|
345
|
+
outputRange: [0, focusRing === 'solid' ? 1 : 0.22],
|
|
294
346
|
}),
|
|
295
347
|
borderColor: theme.colors.borderFocused,
|
|
296
|
-
borderRadius: theme.spacing.borderRadius + 2,
|
|
297
348
|
},
|
|
298
349
|
]}
|
|
299
350
|
/>
|
|
@@ -332,12 +383,14 @@ export const FieldFlowInput = forwardRef<TextInput, FieldFlowInputProps>(functio
|
|
|
332
383
|
placeholderTextColor={theme.colors.placeholder}
|
|
333
384
|
keyboardType={fieldConfig.keyboardType}
|
|
334
385
|
secureTextEntry={fieldConfig.secureTextEntry && !showPassword}
|
|
335
|
-
autoCapitalize={fieldConfig.autoCapitalize}
|
|
336
|
-
autoCorrect={fieldConfig.autoCorrect}
|
|
386
|
+
autoCapitalize={autofillProps?.autoCapitalize ?? fieldConfig.autoCapitalize}
|
|
387
|
+
autoCorrect={autofillProps?.autoCorrect ?? fieldConfig.autoCorrect}
|
|
337
388
|
multiline={fieldConfig.multiline && type !== 'chat'}
|
|
338
389
|
maxLength={maxLength}
|
|
339
390
|
returnKeyType={fieldConfig.multiline ? 'default' : 'done'}
|
|
391
|
+
inputAccessoryViewID={inputAccessoryViewID}
|
|
340
392
|
style={[styles.input, inputStyle]}
|
|
393
|
+
{...autofillProps}
|
|
341
394
|
{...rest}
|
|
342
395
|
/>
|
|
343
396
|
|
|
@@ -383,13 +436,18 @@ export const FieldFlowInput = forwardRef<TextInput, FieldFlowInputProps>(functio
|
|
|
383
436
|
|
|
384
437
|
{(helperText || resolvedError || successText || (showCharacterCount && maxLength)) && (
|
|
385
438
|
<Animated.View style={[styles.footerRow, { opacity: fieldMotion.messageOpacity }]}>
|
|
386
|
-
<View style={
|
|
439
|
+
<View style={styles.helperRow}>
|
|
440
|
+
{helperIconName && (resolvedError || successText || helperText) ? (
|
|
441
|
+
<View style={{ marginTop: 1 }}>
|
|
442
|
+
{renderFieldFlowIcon(helperIconName, helperIconColor, 13)}
|
|
443
|
+
</View>
|
|
444
|
+
) : null}
|
|
387
445
|
{resolvedError ? (
|
|
388
|
-
<Text style={[
|
|
446
|
+
<Text style={[helperTextStyle, errorStyle, helperStyle]}>{resolvedError}</Text>
|
|
389
447
|
) : successText ? (
|
|
390
|
-
<Text style={[
|
|
448
|
+
<Text style={[helperTextStyle, helperStyle]}>{successText}</Text>
|
|
391
449
|
) : helperText ? (
|
|
392
|
-
<Text style={[
|
|
450
|
+
<Text style={[helperTextStyle, helperStyle]}>{helperText}</Text>
|
|
393
451
|
) : null}
|
|
394
452
|
</View>
|
|
395
453
|
{showCharacterCount && maxLength ? (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { StyleSheet } from 'react-native';
|
|
2
2
|
import type { FieldFlowState, FieldFlowTheme, FieldFlowVariant } from '../../types';
|
|
3
|
+
import { surfaceShadow } from '../../theme/appearance';
|
|
3
4
|
|
|
4
5
|
export function borderColorFor(theme: FieldFlowTheme, state: FieldFlowState): string {
|
|
5
6
|
switch (state) {
|
|
@@ -33,34 +34,51 @@ export function createStyles(
|
|
|
33
34
|
const isUnderline = variant === 'underline';
|
|
34
35
|
const isFilled = variant === 'filled';
|
|
35
36
|
const isFloating = variant === 'floating';
|
|
37
|
+
const appearance = theme.appearance;
|
|
38
|
+
const surface = appearance?.surface ?? 'flat';
|
|
39
|
+
const focusRing = appearance?.focusRing ?? 'softGlow';
|
|
36
40
|
const borderW = state === 'focused' ? theme.spacing.borderWidthFocused : theme.spacing.borderWidth;
|
|
41
|
+
const shadow = !isUnderline ? surfaceShadow(surface) : null;
|
|
42
|
+
|
|
43
|
+
let backgroundColor = theme.colors.background;
|
|
44
|
+
if (state === 'disabled') backgroundColor = theme.colors.backgroundDisabled;
|
|
45
|
+
else if (isFilled || surface === 'soft') backgroundColor = theme.colors.surfaceMuted ?? theme.colors.backgroundDisabled;
|
|
46
|
+
else if (surface === 'inset') backgroundColor = theme.colors.surfaceMuted ?? theme.colors.backgroundDisabled;
|
|
47
|
+
else if (surface === 'elevated') backgroundColor = theme.colors.surfaceElevated ?? theme.colors.background;
|
|
48
|
+
else if (isUnderline) backgroundColor = 'transparent';
|
|
49
|
+
|
|
50
|
+
const glowPad = focusRing === 'solid' ? 2 : focusRing === 'softGlow' ? 3 : 0;
|
|
37
51
|
|
|
38
52
|
return StyleSheet.create({
|
|
39
53
|
wrapper: {
|
|
40
54
|
marginBottom: theme.spacing.gap * 2,
|
|
41
55
|
},
|
|
56
|
+
labelRow: {
|
|
57
|
+
flexDirection: 'row',
|
|
58
|
+
alignItems: 'center',
|
|
59
|
+
gap: 6,
|
|
60
|
+
marginBottom: theme.spacing.gap / 1.5,
|
|
61
|
+
},
|
|
42
62
|
label: {
|
|
43
63
|
fontSize: theme.typography.labelFontSize,
|
|
44
|
-
marginBottom: theme.spacing.gap / 1.5,
|
|
45
64
|
color: labelColorFor(theme, state),
|
|
46
65
|
fontWeight: '500',
|
|
47
66
|
fontFamily: theme.typography.fontFamily,
|
|
67
|
+
flexShrink: 1,
|
|
48
68
|
},
|
|
49
69
|
inputContainer: {
|
|
50
70
|
position: 'relative',
|
|
51
71
|
overflow: 'visible',
|
|
52
72
|
flexDirection: 'row',
|
|
53
73
|
alignItems: multiline ? 'flex-start' : 'center',
|
|
54
|
-
backgroundColor
|
|
55
|
-
? theme.colors.backgroundDisabled
|
|
56
|
-
: isFilled
|
|
57
|
-
? theme.colors.backgroundDisabled
|
|
58
|
-
: isUnderline
|
|
59
|
-
? 'transparent'
|
|
60
|
-
: theme.colors.background,
|
|
74
|
+
backgroundColor,
|
|
61
75
|
borderRadius: isUnderline ? 0 : theme.spacing.borderRadius,
|
|
62
|
-
borderWidth: isUnderline ? 0 : isFilled ? 0 : borderW,
|
|
63
|
-
borderBottomWidth: isUnderline
|
|
76
|
+
borderWidth: isUnderline ? 0 : isFilled && surface !== 'inset' ? 0 : borderW,
|
|
77
|
+
borderBottomWidth: isUnderline
|
|
78
|
+
? borderW + (state === 'focused' && focusRing === 'underlinePulse' ? 1 : state === 'focused' ? 0.5 : 0)
|
|
79
|
+
: isFilled && surface !== 'inset'
|
|
80
|
+
? 0
|
|
81
|
+
: borderW,
|
|
64
82
|
borderColor: borderColorFor(theme, state),
|
|
65
83
|
paddingHorizontal: theme.spacing.paddingHorizontal,
|
|
66
84
|
paddingVertical: multiline ? theme.spacing.paddingVertical : 0,
|
|
@@ -68,14 +86,16 @@ export function createStyles(
|
|
|
68
86
|
? undefined
|
|
69
87
|
: theme.spacing.paddingVertical * 2 + theme.typography.fontSize + 6 + (isFloating ? 6 : 0),
|
|
70
88
|
paddingTop: isFloating && !multiline ? theme.spacing.paddingVertical + 6 : undefined,
|
|
89
|
+
...(shadow ?? null),
|
|
71
90
|
},
|
|
72
91
|
focusGlow: {
|
|
73
92
|
position: 'absolute',
|
|
74
|
-
top: -
|
|
75
|
-
left: -
|
|
76
|
-
right: -
|
|
77
|
-
bottom: -
|
|
78
|
-
borderWidth: 3,
|
|
93
|
+
top: -glowPad,
|
|
94
|
+
left: -glowPad,
|
|
95
|
+
right: -glowPad,
|
|
96
|
+
bottom: -glowPad,
|
|
97
|
+
borderWidth: focusRing === 'solid' ? 2 : focusRing === 'softGlow' ? 3 : 0,
|
|
98
|
+
borderRadius: theme.spacing.borderRadius + glowPad,
|
|
79
99
|
},
|
|
80
100
|
affixText: {
|
|
81
101
|
fontSize: theme.typography.fontSize,
|
|
@@ -86,7 +106,9 @@ export function createStyles(
|
|
|
86
106
|
floatingLabel: {
|
|
87
107
|
position: 'absolute',
|
|
88
108
|
left: theme.spacing.paddingHorizontal,
|
|
89
|
-
backgroundColor: isFilled
|
|
109
|
+
backgroundColor: isFilled || surface === 'soft' || surface === 'inset'
|
|
110
|
+
? theme.colors.surfaceMuted ?? theme.colors.backgroundDisabled
|
|
111
|
+
: theme.colors.background,
|
|
90
112
|
paddingHorizontal: 4,
|
|
91
113
|
zIndex: 2,
|
|
92
114
|
fontFamily: theme.typography.fontFamily,
|
|
@@ -114,8 +136,16 @@ export function createStyles(
|
|
|
114
136
|
},
|
|
115
137
|
footerRow: {
|
|
116
138
|
flexDirection: 'row',
|
|
139
|
+
alignItems: 'flex-start',
|
|
117
140
|
justifyContent: 'space-between',
|
|
118
141
|
marginTop: theme.spacing.gap / 1.5,
|
|
142
|
+
gap: 6,
|
|
143
|
+
},
|
|
144
|
+
helperRow: {
|
|
145
|
+
flex: 1,
|
|
146
|
+
flexDirection: 'row',
|
|
147
|
+
alignItems: 'flex-start',
|
|
148
|
+
gap: 6,
|
|
119
149
|
},
|
|
120
150
|
helperText: {
|
|
121
151
|
fontSize: theme.typography.helperFontSize,
|
|
@@ -135,6 +165,18 @@ export function createStyles(
|
|
|
135
165
|
flexShrink: 1,
|
|
136
166
|
fontFamily: theme.typography.fontFamily,
|
|
137
167
|
},
|
|
168
|
+
warningText: {
|
|
169
|
+
fontSize: theme.typography.helperFontSize,
|
|
170
|
+
color: theme.colors.warning,
|
|
171
|
+
flexShrink: 1,
|
|
172
|
+
fontFamily: theme.typography.fontFamily,
|
|
173
|
+
},
|
|
174
|
+
infoText: {
|
|
175
|
+
fontSize: theme.typography.helperFontSize,
|
|
176
|
+
color: theme.colors.info,
|
|
177
|
+
flexShrink: 1,
|
|
178
|
+
fontFamily: theme.typography.fontFamily,
|
|
179
|
+
},
|
|
138
180
|
charCount: {
|
|
139
181
|
fontSize: theme.typography.helperFontSize,
|
|
140
182
|
color: theme.colors.placeholder,
|
|
@@ -4,6 +4,7 @@ import type { FieldFlowFieldType, FieldFlowRules, FieldFlowVariant, DeepPartial,
|
|
|
4
4
|
import type { FieldFlowIconSource } from '../../icons';
|
|
5
5
|
import type { FieldFlowAffordanceChip, FieldFlowAffordanceKind } from '../../utils/affordance';
|
|
6
6
|
import type { PasswordStrengthRule } from '../../utils/passwordStrength';
|
|
7
|
+
import type { FieldFlowAutofillRole } from '../../autofill/profiles';
|
|
7
8
|
|
|
8
9
|
export interface FieldFlowInputProps
|
|
9
10
|
extends Omit<TextInputProps, 'style' | 'onChangeText' | 'onFocus' | 'onBlur' | 'onSubmitEditing'> {
|
|
@@ -89,6 +90,26 @@ export interface FieldFlowInputProps
|
|
|
89
90
|
*/
|
|
90
91
|
motion?: boolean;
|
|
91
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Platform autofill role (`email`, `password`, `oneTimeCode`, …).
|
|
95
|
+
* When omitted, a sensible default is inferred from `type` (email/phone/password).
|
|
96
|
+
* Pass `off` to disable. See `autofillProfiles` for screen bundles.
|
|
97
|
+
*/
|
|
98
|
+
autofill?: FieldFlowAutofillRole;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* iOS keyboard accessory id — pair with `FieldFlowKeyboardToolbar` `nativeID`.
|
|
102
|
+
*/
|
|
103
|
+
inputAccessoryViewID?: string;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Soft status icon beside helper / success / warning copy.
|
|
107
|
+
* Error state always shows an alert icon when helperStatus is unset.
|
|
108
|
+
*/
|
|
109
|
+
helperStatus?: 'info' | 'success' | 'warning' | 'error' | 'none';
|
|
110
|
+
/** Tip text — renders a `FieldFlowInfoTip` next to the label. */
|
|
111
|
+
infoTip?: string;
|
|
112
|
+
|
|
92
113
|
maxLength?: number;
|
|
93
114
|
showCharacterCount?: boolean;
|
|
94
115
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
InputAccessoryView,
|
|
4
|
+
Keyboard,
|
|
5
|
+
Platform,
|
|
6
|
+
Pressable,
|
|
7
|
+
StyleSheet,
|
|
8
|
+
Text,
|
|
9
|
+
View,
|
|
10
|
+
type ViewStyle,
|
|
11
|
+
} from 'react-native';
|
|
12
|
+
import { useFieldFlowTheme, mergeDeep } from '../../theme/ThemeProvider';
|
|
13
|
+
import { defaultTheme } from '../../theme/defaultTheme';
|
|
14
|
+
import type { DeepPartial, FieldFlowTheme } from '../../types';
|
|
15
|
+
|
|
16
|
+
export interface FieldFlowKeyboardToolbarProps {
|
|
17
|
+
/** Must match `inputAccessoryViewID` on each TextInput (iOS). */
|
|
18
|
+
nativeID: string;
|
|
19
|
+
onPrevious?: () => void;
|
|
20
|
+
onNext?: () => void;
|
|
21
|
+
onDone?: () => void;
|
|
22
|
+
canPrevious?: boolean;
|
|
23
|
+
canNext?: boolean;
|
|
24
|
+
previousLabel?: string;
|
|
25
|
+
nextLabel?: string;
|
|
26
|
+
doneLabel?: string;
|
|
27
|
+
/** Also render a floating bar on Android (above keyboard region via absolute layout in parent). */
|
|
28
|
+
androidFloating?: boolean;
|
|
29
|
+
visible?: boolean;
|
|
30
|
+
containerStyle?: ViewStyle;
|
|
31
|
+
theme?: DeepPartial<FieldFlowTheme>;
|
|
32
|
+
testID?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Prev / Next / Done accessory above the keyboard.
|
|
37
|
+
* iOS: native `InputAccessoryView`. Android: optional floating bar (`androidFloating`).
|
|
38
|
+
* Pair with `useKeyboardManager` focus helpers.
|
|
39
|
+
*/
|
|
40
|
+
export function FieldFlowKeyboardToolbar({
|
|
41
|
+
nativeID,
|
|
42
|
+
onPrevious,
|
|
43
|
+
onNext,
|
|
44
|
+
onDone,
|
|
45
|
+
canPrevious = true,
|
|
46
|
+
canNext = true,
|
|
47
|
+
previousLabel = 'Previous',
|
|
48
|
+
nextLabel = 'Next',
|
|
49
|
+
doneLabel = 'Done',
|
|
50
|
+
androidFloating = true,
|
|
51
|
+
visible = true,
|
|
52
|
+
containerStyle,
|
|
53
|
+
theme: themeOverride,
|
|
54
|
+
testID,
|
|
55
|
+
}: FieldFlowKeyboardToolbarProps) {
|
|
56
|
+
const contextTheme = useFieldFlowTheme();
|
|
57
|
+
const theme = useMemo(
|
|
58
|
+
() => (themeOverride ? mergeDeep(contextTheme ?? defaultTheme, themeOverride) : contextTheme ?? defaultTheme),
|
|
59
|
+
[contextTheme, themeOverride],
|
|
60
|
+
);
|
|
61
|
+
const styles = useMemo(() => createStyles(theme), [theme]);
|
|
62
|
+
|
|
63
|
+
const bar = (
|
|
64
|
+
<View style={[styles.bar, containerStyle]} testID={testID}>
|
|
65
|
+
<Pressable
|
|
66
|
+
onPress={onPrevious}
|
|
67
|
+
disabled={!canPrevious || !onPrevious}
|
|
68
|
+
style={[styles.btn, (!canPrevious || !onPrevious) && styles.btnDisabled]}
|
|
69
|
+
accessibilityRole="button"
|
|
70
|
+
accessibilityLabel={previousLabel}
|
|
71
|
+
>
|
|
72
|
+
<Text style={styles.btnText}>{previousLabel}</Text>
|
|
73
|
+
</Pressable>
|
|
74
|
+
<Pressable
|
|
75
|
+
onPress={onNext}
|
|
76
|
+
disabled={!canNext || !onNext}
|
|
77
|
+
style={[styles.btn, (!canNext || !onNext) && styles.btnDisabled]}
|
|
78
|
+
accessibilityRole="button"
|
|
79
|
+
accessibilityLabel={nextLabel}
|
|
80
|
+
>
|
|
81
|
+
<Text style={styles.btnText}>{nextLabel}</Text>
|
|
82
|
+
</Pressable>
|
|
83
|
+
<View style={{ flex: 1 }} />
|
|
84
|
+
<Pressable
|
|
85
|
+
onPress={() => {
|
|
86
|
+
onDone?.();
|
|
87
|
+
Keyboard.dismiss();
|
|
88
|
+
}}
|
|
89
|
+
style={styles.done}
|
|
90
|
+
accessibilityRole="button"
|
|
91
|
+
accessibilityLabel={doneLabel}
|
|
92
|
+
>
|
|
93
|
+
<Text style={styles.doneText}>{doneLabel}</Text>
|
|
94
|
+
</Pressable>
|
|
95
|
+
</View>
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
if (!visible) return null;
|
|
99
|
+
|
|
100
|
+
if (Platform.OS === 'ios') {
|
|
101
|
+
return (
|
|
102
|
+
<InputAccessoryView nativeID={nativeID} backgroundColor={theme.colors.backgroundDisabled}>
|
|
103
|
+
{bar}
|
|
104
|
+
</InputAccessoryView>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!androidFloating) return null;
|
|
109
|
+
return <View style={styles.androidWrap}>{bar}</View>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Stable default accessory id for FieldFlow forms. */
|
|
113
|
+
export const FIELD_FLOW_KEYBOARD_TOOLBAR_ID = 'fieldflow-keyboard-toolbar';
|
|
114
|
+
|
|
115
|
+
function createStyles(theme: FieldFlowTheme) {
|
|
116
|
+
return StyleSheet.create({
|
|
117
|
+
androidWrap: {
|
|
118
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
119
|
+
borderTopColor: theme.colors.border,
|
|
120
|
+
},
|
|
121
|
+
bar: {
|
|
122
|
+
flexDirection: 'row',
|
|
123
|
+
alignItems: 'center',
|
|
124
|
+
paddingHorizontal: 10,
|
|
125
|
+
paddingVertical: 8,
|
|
126
|
+
backgroundColor: theme.colors.backgroundDisabled,
|
|
127
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
128
|
+
borderTopColor: theme.colors.border,
|
|
129
|
+
gap: 4,
|
|
130
|
+
},
|
|
131
|
+
btn: { paddingHorizontal: 10, paddingVertical: 6 },
|
|
132
|
+
btnDisabled: { opacity: 0.35 },
|
|
133
|
+
btnText: {
|
|
134
|
+
fontSize: theme.typography.helperFontSize + 1,
|
|
135
|
+
color: theme.colors.borderFocused,
|
|
136
|
+
fontWeight: '600',
|
|
137
|
+
},
|
|
138
|
+
done: {
|
|
139
|
+
paddingHorizontal: 12,
|
|
140
|
+
paddingVertical: 6,
|
|
141
|
+
borderRadius: 8,
|
|
142
|
+
backgroundColor: theme.colors.borderFocused,
|
|
143
|
+
},
|
|
144
|
+
doneText: {
|
|
145
|
+
fontSize: theme.typography.helperFontSize + 1,
|
|
146
|
+
color: '#fff',
|
|
147
|
+
fontWeight: '700',
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { Animated, Easing, StyleSheet, View, type ViewStyle } from 'react-native';
|
|
3
|
+
import { useFieldFlowTheme, mergeDeep } from '../../theme/ThemeProvider';
|
|
4
|
+
import { defaultTheme } from '../../theme/defaultTheme';
|
|
5
|
+
import { useReducedMotion } from '../../motion/useReducedMotion';
|
|
6
|
+
import type { DeepPartial, FieldFlowTheme, FieldFlowVariant } from '../../types';
|
|
7
|
+
|
|
8
|
+
export interface FieldFlowSkeletonProps {
|
|
9
|
+
/** How many fake fields to show. Default: 3. */
|
|
10
|
+
count?: number;
|
|
11
|
+
/** Match your form variant for realistic height. */
|
|
12
|
+
variant?: FieldFlowVariant;
|
|
13
|
+
/** Show label bars. Default: true. */
|
|
14
|
+
showLabels?: boolean;
|
|
15
|
+
/** Multiline taller block on last item. */
|
|
16
|
+
lastMultiline?: boolean;
|
|
17
|
+
containerStyle?: ViewStyle;
|
|
18
|
+
theme?: DeepPartial<FieldFlowTheme>;
|
|
19
|
+
testID?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Soft shimmer placeholders while a form hydrates (draft / API).
|
|
24
|
+
*/
|
|
25
|
+
export function FieldFlowSkeleton({
|
|
26
|
+
count = 3,
|
|
27
|
+
variant = 'outlined',
|
|
28
|
+
showLabels = true,
|
|
29
|
+
lastMultiline = false,
|
|
30
|
+
containerStyle,
|
|
31
|
+
theme: themeOverride,
|
|
32
|
+
testID,
|
|
33
|
+
}: FieldFlowSkeletonProps) {
|
|
34
|
+
const contextTheme = useFieldFlowTheme();
|
|
35
|
+
const theme = useMemo(
|
|
36
|
+
() => (themeOverride ? mergeDeep(contextTheme ?? defaultTheme, themeOverride) : contextTheme ?? defaultTheme),
|
|
37
|
+
[contextTheme, themeOverride],
|
|
38
|
+
);
|
|
39
|
+
const styles = useMemo(() => createStyles(theme, variant), [theme, variant]);
|
|
40
|
+
const reducedMotion = useReducedMotion();
|
|
41
|
+
const pulse = useRef(new Animated.Value(0.45)).current;
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (reducedMotion) {
|
|
45
|
+
pulse.setValue(0.55);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const loop = Animated.loop(
|
|
49
|
+
Animated.sequence([
|
|
50
|
+
Animated.timing(pulse, {
|
|
51
|
+
toValue: 0.9,
|
|
52
|
+
duration: 700,
|
|
53
|
+
easing: Easing.inOut(Easing.quad),
|
|
54
|
+
useNativeDriver: true,
|
|
55
|
+
}),
|
|
56
|
+
Animated.timing(pulse, {
|
|
57
|
+
toValue: 0.45,
|
|
58
|
+
duration: 700,
|
|
59
|
+
easing: Easing.inOut(Easing.quad),
|
|
60
|
+
useNativeDriver: true,
|
|
61
|
+
}),
|
|
62
|
+
]),
|
|
63
|
+
);
|
|
64
|
+
loop.start();
|
|
65
|
+
return () => loop.stop();
|
|
66
|
+
}, [reducedMotion, pulse]);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<View style={[styles.wrap, containerStyle]} testID={testID} accessibilityLabel="Loading form">
|
|
70
|
+
{Array.from({ length: count }, (_, i) => {
|
|
71
|
+
const tall = lastMultiline && i === count - 1;
|
|
72
|
+
return (
|
|
73
|
+
<View key={i} style={styles.item}>
|
|
74
|
+
{showLabels ? <Animated.View style={[styles.label, { opacity: pulse }]} /> : null}
|
|
75
|
+
<Animated.View style={[styles.field, tall && styles.fieldTall, { opacity: pulse }]} />
|
|
76
|
+
</View>
|
|
77
|
+
);
|
|
78
|
+
})}
|
|
79
|
+
</View>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function createStyles(theme: FieldFlowTheme, variant: FieldFlowVariant) {
|
|
84
|
+
const height =
|
|
85
|
+
theme.spacing.paddingVertical * 2 + theme.typography.fontSize + 6 + (variant === 'floating' ? 6 : 0);
|
|
86
|
+
return StyleSheet.create({
|
|
87
|
+
wrap: { marginBottom: theme.spacing.gap },
|
|
88
|
+
item: { marginBottom: theme.spacing.gap * 2 },
|
|
89
|
+
label: {
|
|
90
|
+
width: '32%',
|
|
91
|
+
height: theme.typography.labelFontSize,
|
|
92
|
+
borderRadius: 4,
|
|
93
|
+
backgroundColor: theme.colors.backgroundDisabled,
|
|
94
|
+
marginBottom: theme.spacing.gap,
|
|
95
|
+
},
|
|
96
|
+
field: {
|
|
97
|
+
height,
|
|
98
|
+
borderRadius: theme.spacing.borderRadius,
|
|
99
|
+
backgroundColor: theme.colors.backgroundDisabled,
|
|
100
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
101
|
+
borderColor: theme.colors.border,
|
|
102
|
+
},
|
|
103
|
+
fieldTall: { height: 88 },
|
|
104
|
+
});
|
|
105
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
2
|
import { KeyboardAvoidingView, Platform, ScrollView, StyleSheet, ViewStyle } from 'react-native';
|
|
3
3
|
import type { UseKeyboardManagerResult } from '../../hooks/useKeyboardManager';
|
|
4
4
|
|
|
5
5
|
export interface KeyboardAwareFormWrapperProps {
|
|
6
|
-
|
|
6
|
+
/** Optional — omit when you only need keyboard-avoiding scroll without Next/Done chaining. */
|
|
7
|
+
keyboardManager?: Pick<UseKeyboardManagerResult, 'scrollViewRef'>;
|
|
7
8
|
children: React.ReactNode;
|
|
8
9
|
contentContainerStyle?: ViewStyle;
|
|
9
10
|
style?: ViewStyle;
|
|
@@ -13,7 +14,7 @@ export interface KeyboardAwareFormWrapperProps {
|
|
|
13
14
|
* Wrap your form fields in this instead of a plain ScrollView. It automatically
|
|
14
15
|
* keeps the focused field visible above the keyboard and animates smoothly on
|
|
15
16
|
* both iOS and Android; pass it the same `keyboardManager` you got from
|
|
16
|
-
* `useKeyboardManager()
|
|
17
|
+
* `useKeyboardManager()` when you want auto-scroll + Next/Done focus.
|
|
17
18
|
*/
|
|
18
19
|
export function KeyboardAwareFormWrapper({
|
|
19
20
|
keyboardManager,
|
|
@@ -21,6 +22,9 @@ export function KeyboardAwareFormWrapper({
|
|
|
21
22
|
contentContainerStyle,
|
|
22
23
|
style,
|
|
23
24
|
}: KeyboardAwareFormWrapperProps) {
|
|
25
|
+
const fallbackScrollRef = useRef<ScrollView>(null);
|
|
26
|
+
const scrollRef = keyboardManager?.scrollViewRef ?? fallbackScrollRef;
|
|
27
|
+
|
|
24
28
|
return (
|
|
25
29
|
<KeyboardAvoidingView
|
|
26
30
|
style={[styles.flex, style]}
|
|
@@ -28,7 +32,7 @@ export function KeyboardAwareFormWrapper({
|
|
|
28
32
|
keyboardVerticalOffset={Platform.OS === 'ios' ? 64 : 0}
|
|
29
33
|
>
|
|
30
34
|
<ScrollView
|
|
31
|
-
ref={
|
|
35
|
+
ref={scrollRef}
|
|
32
36
|
keyboardShouldPersistTaps="handled"
|
|
33
37
|
contentContainerStyle={[styles.content, contentContainerStyle]}
|
|
34
38
|
showsVerticalScrollIndicator={false}
|
package/src/hooks/index.ts
CHANGED
|
@@ -44,3 +44,20 @@ export type {
|
|
|
44
44
|
UseFieldFlowDirtyGuardOptions,
|
|
45
45
|
UseFieldFlowDirtyGuardResult,
|
|
46
46
|
} from './useFieldFlowDirtyGuard';
|
|
47
|
+
|
|
48
|
+
export { useFieldFlowCompleteness } from './useFieldFlowCompleteness';
|
|
49
|
+
export type {
|
|
50
|
+
UseFieldFlowCompletenessOptions,
|
|
51
|
+
UseFieldFlowCompletenessResult,
|
|
52
|
+
} from './useFieldFlowCompleteness';
|
|
53
|
+
|
|
54
|
+
export { useFieldFlowSubmitQueue } from './useFieldFlowSubmitQueue';
|
|
55
|
+
export type {
|
|
56
|
+
FieldFlowSubmitJob,
|
|
57
|
+
FieldFlowSubmitQueueStorage,
|
|
58
|
+
UseFieldFlowSubmitQueueOptions,
|
|
59
|
+
UseFieldFlowSubmitQueueResult,
|
|
60
|
+
} from './useFieldFlowSubmitQueue';
|
|
61
|
+
|
|
62
|
+
export { useSmartPaste } from './useSmartPaste';
|
|
63
|
+
export type { UseSmartPasteOptions } from './useSmartPaste';
|