@jobber/components-native 0.21.1 → 0.22.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/dist/src/InputText/InputText.js +138 -0
- package/dist/src/InputText/InputText.style.js +20 -0
- package/dist/src/InputText/context/InputAccessoriesContext.js +17 -0
- package/dist/src/InputText/context/InputAccessoriesProvider.js +73 -0
- package/dist/src/InputText/context/InputAccessoriesProvider.style.js +21 -0
- package/dist/src/InputText/context/InputAccessory.style.js +16 -0
- package/dist/src/InputText/context/index.js +2 -0
- package/dist/src/InputText/context/types.js +1 -0
- package/dist/src/InputText/index.js +2 -0
- package/dist/src/hooks/index.js +1 -0
- package/dist/src/hooks/useFormController.js +38 -0
- package/dist/src/index.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/src/InputText/InputText.d.ts +165 -0
- package/dist/types/src/InputText/InputText.style.d.ts +16 -0
- package/dist/types/src/InputText/context/InputAccessoriesContext.d.ts +4 -0
- package/dist/types/src/InputText/context/InputAccessoriesProvider.d.ts +4 -0
- package/dist/types/src/InputText/context/InputAccessoriesProvider.style.d.ts +17 -0
- package/dist/types/src/InputText/context/InputAccessory.style.d.ts +14 -0
- package/dist/types/src/InputText/context/index.d.ts +2 -0
- package/dist/types/src/InputText/context/types.d.ts +23 -0
- package/dist/types/src/InputText/index.d.ts +3 -0
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/useFormController.d.ts +12 -0
- package/dist/types/src/index.d.ts +1 -0
- package/package.json +4 -2
- package/src/InputText/InputText.style.ts +25 -0
- package/src/InputText/InputText.test.tsx +534 -0
- package/src/InputText/InputText.tsx +483 -0
- package/src/InputText/context/InputAccessoriesContext.ts +21 -0
- package/src/InputText/context/InputAccessoriesProvider.style.tsx +23 -0
- package/src/InputText/context/InputAccessoriesProvider.test.tsx +84 -0
- package/src/InputText/context/InputAccessoriesProvider.tsx +121 -0
- package/src/InputText/context/InputAccessory.style.ts +17 -0
- package/src/InputText/context/index.ts +2 -0
- package/src/InputText/context/types.ts +28 -0
- package/src/InputText/index.ts +3 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useFormController.ts +68 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React, { ReactNode, useEffect, useRef, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
InputAccessoryView,
|
|
4
|
+
Keyboard,
|
|
5
|
+
Platform,
|
|
6
|
+
// eslint-disable-next-line no-restricted-imports
|
|
7
|
+
Button as RNButton,
|
|
8
|
+
View,
|
|
9
|
+
useColorScheme,
|
|
10
|
+
} from "react-native";
|
|
11
|
+
import { v4 } from "react-native-uuid";
|
|
12
|
+
import { InputAccessoriesContext } from "./InputAccessoriesContext";
|
|
13
|
+
import { styles } from "./InputAccessoriesProvider.style";
|
|
14
|
+
|
|
15
|
+
export function InputAccessoriesProvider({
|
|
16
|
+
children,
|
|
17
|
+
}: {
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
}): JSX.Element {
|
|
20
|
+
const inputAccessoryID = useRef(v4()).current;
|
|
21
|
+
const {
|
|
22
|
+
focusedInput,
|
|
23
|
+
setFocusedInput,
|
|
24
|
+
canFocusNext,
|
|
25
|
+
canFocusPrevious,
|
|
26
|
+
elements,
|
|
27
|
+
setElements,
|
|
28
|
+
previousKey,
|
|
29
|
+
nextKey,
|
|
30
|
+
} = useInputAccessoriesProviderState();
|
|
31
|
+
|
|
32
|
+
const colorScheme = useColorScheme();
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<InputAccessoriesContext.Provider
|
|
36
|
+
value={{
|
|
37
|
+
elements,
|
|
38
|
+
focusedInput,
|
|
39
|
+
canFocusNext,
|
|
40
|
+
canFocusPrevious,
|
|
41
|
+
inputAccessoryID,
|
|
42
|
+
register,
|
|
43
|
+
unregister,
|
|
44
|
+
onFocusNext,
|
|
45
|
+
onFocusPrevious,
|
|
46
|
+
setFocusedInput,
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
{children}
|
|
50
|
+
{Platform.OS === "ios" && (
|
|
51
|
+
<InputAccessoryView nativeID={inputAccessoryID}>
|
|
52
|
+
<View
|
|
53
|
+
testID="ATL-InputAccessory"
|
|
54
|
+
style={[
|
|
55
|
+
styles.container,
|
|
56
|
+
colorScheme === "dark" ? styles.darkTheme : styles.lightTheme,
|
|
57
|
+
]}
|
|
58
|
+
>
|
|
59
|
+
<RNButton
|
|
60
|
+
onPress={Keyboard.dismiss}
|
|
61
|
+
title="Done"
|
|
62
|
+
testID="ATL-InputAccessory-Done"
|
|
63
|
+
color={colorScheme === "dark" ? "white" : undefined}
|
|
64
|
+
/>
|
|
65
|
+
</View>
|
|
66
|
+
</InputAccessoryView>
|
|
67
|
+
)}
|
|
68
|
+
</InputAccessoriesContext.Provider>
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
function register(name: string, onFocus: () => void) {
|
|
72
|
+
elements[name] = onFocus;
|
|
73
|
+
setElements(elements);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function unregister(name: string) {
|
|
77
|
+
delete elements[name];
|
|
78
|
+
setElements(elements);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function onFocusNext() {
|
|
82
|
+
const nextElement = elements[nextKey];
|
|
83
|
+
nextElement?.();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function onFocusPrevious() {
|
|
87
|
+
const previousElement = elements[previousKey];
|
|
88
|
+
previousElement?.();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function useInputAccessoriesProviderState() {
|
|
93
|
+
const [focusedInput, setFocusedInput] = useState("");
|
|
94
|
+
const [canFocusNext, setCanFocusNext] = useState(false);
|
|
95
|
+
const [canFocusPrevious, setCanFocusPrevious] = useState(false);
|
|
96
|
+
const [elements, setElements] = useState<Record<string, () => void>>({});
|
|
97
|
+
|
|
98
|
+
const keys = Object.keys(elements);
|
|
99
|
+
const selectedIndex = keys.findIndex(key => key === focusedInput);
|
|
100
|
+
|
|
101
|
+
const nextKey = keys[selectedIndex + 1];
|
|
102
|
+
const previousKey = keys[selectedIndex - 1];
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
setCanFocusNext(Boolean(nextKey));
|
|
106
|
+
setCanFocusPrevious(Boolean(previousKey));
|
|
107
|
+
}, [previousKey, nextKey]);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
previousKey,
|
|
111
|
+
nextKey,
|
|
112
|
+
focusedInput,
|
|
113
|
+
setFocusedInput,
|
|
114
|
+
canFocusNext,
|
|
115
|
+
setCanFocusNext,
|
|
116
|
+
canFocusPrevious,
|
|
117
|
+
setCanFocusPrevious,
|
|
118
|
+
elements,
|
|
119
|
+
setElements,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
|
+
import { tokens } from "../../utils/design";
|
|
3
|
+
|
|
4
|
+
export const styles = StyleSheet.create({
|
|
5
|
+
container: {
|
|
6
|
+
flexDirection: "row",
|
|
7
|
+
justifyContent: "space-between",
|
|
8
|
+
alignItems: "center",
|
|
9
|
+
paddingHorizontal: tokens["space-small"],
|
|
10
|
+
backgroundColor: tokens["color-surface--background"],
|
|
11
|
+
borderTopWidth: tokens["space-minuscule"],
|
|
12
|
+
borderTopColor: tokens["color-border"],
|
|
13
|
+
},
|
|
14
|
+
buttonContainer: {
|
|
15
|
+
flexDirection: "row",
|
|
16
|
+
},
|
|
17
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
type Name = string;
|
|
2
|
+
|
|
3
|
+
export interface InputAccessoriesContextProps {
|
|
4
|
+
/**
|
|
5
|
+
* Registered elements.
|
|
6
|
+
*/
|
|
7
|
+
readonly elements: Record<Name, () => void>;
|
|
8
|
+
|
|
9
|
+
readonly focusedInput: string;
|
|
10
|
+
|
|
11
|
+
readonly canFocusNext: boolean;
|
|
12
|
+
readonly canFocusPrevious: boolean;
|
|
13
|
+
readonly inputAccessoryID?: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Registers the element to the context.
|
|
17
|
+
*/
|
|
18
|
+
readonly register: (name: Name, onFocus: () => void) => void;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Un-registers the element from the context.
|
|
22
|
+
*/
|
|
23
|
+
readonly unregister: (name: Name) => void;
|
|
24
|
+
|
|
25
|
+
readonly onFocusNext: () => void;
|
|
26
|
+
readonly onFocusPrevious: () => void;
|
|
27
|
+
readonly setFocusedInput: (name: string) => void;
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useFormController";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { v1 } from "react-native-uuid";
|
|
2
|
+
import {
|
|
3
|
+
FieldError,
|
|
4
|
+
RegisterOptions,
|
|
5
|
+
UseControllerReturn,
|
|
6
|
+
useController,
|
|
7
|
+
useForm,
|
|
8
|
+
useFormContext,
|
|
9
|
+
} from "react-hook-form";
|
|
10
|
+
import { useState } from "react";
|
|
11
|
+
|
|
12
|
+
interface UseFormControllerProps<T> {
|
|
13
|
+
name?: string;
|
|
14
|
+
value?: T;
|
|
15
|
+
validations?: RegisterOptions;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface UseFormController {
|
|
19
|
+
error?: FieldError;
|
|
20
|
+
field: UseControllerReturn["field"];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function useFormController<T>({
|
|
24
|
+
name,
|
|
25
|
+
value,
|
|
26
|
+
validations,
|
|
27
|
+
}: UseFormControllerProps<T>): UseFormController {
|
|
28
|
+
const fieldName = useControlName(name);
|
|
29
|
+
|
|
30
|
+
const form = useForm<{ fieldName: T }>({
|
|
31
|
+
mode: "onTouched",
|
|
32
|
+
defaultValues: { [fieldName]: value },
|
|
33
|
+
});
|
|
34
|
+
const formContext = useFormContext();
|
|
35
|
+
|
|
36
|
+
const {
|
|
37
|
+
control,
|
|
38
|
+
formState: { errors },
|
|
39
|
+
} = formContext || form;
|
|
40
|
+
|
|
41
|
+
const { field } = useController({
|
|
42
|
+
name: fieldName,
|
|
43
|
+
control,
|
|
44
|
+
rules: validations,
|
|
45
|
+
defaultValue: value || undefined,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// The naming convention established by react-hook-form for arrays of fields is, for example, "emails.0.description".
|
|
49
|
+
// This corresponds to the structure of the error object, e.g. { emails: { 0: { description: "foobar" } } }
|
|
50
|
+
// We assume here that fields will either follow this period-delimited three-part convention, or else that they are simple and indivisible (e.g. "city").
|
|
51
|
+
// TODO: Add support for two-part identifiers (e.g. "property.province")
|
|
52
|
+
const fieldIdentifiers = fieldName.split(".");
|
|
53
|
+
let error: FieldError | undefined;
|
|
54
|
+
if (fieldIdentifiers.length === 3) {
|
|
55
|
+
const [section, item, identifier] = fieldIdentifiers;
|
|
56
|
+
error = errors[section]?.[item]?.[identifier];
|
|
57
|
+
} else {
|
|
58
|
+
error = errors[fieldName];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { error, field };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function useControlName(name?: string): UseControllerReturn["field"]["name"] {
|
|
65
|
+
const [identifier] = useState(v1());
|
|
66
|
+
const prefix = `generatedName--${identifier}`;
|
|
67
|
+
return `${name || prefix}` as const;
|
|
68
|
+
}
|
package/src/index.ts
CHANGED