@jobber/components-native 0.21.1 → 0.23.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/Switch/Switch.js +16 -0
- package/dist/src/Switch/Switch.styles.js +20 -0
- package/dist/src/Switch/components/BaseSwitch/BaseSwitch.js +54 -0
- package/dist/src/Switch/components/BaseSwitch/index.js +1 -0
- package/dist/src/Switch/index.js +1 -0
- package/dist/src/hooks/index.js +1 -0
- package/dist/src/hooks/useFormController.js +38 -0
- package/dist/src/index.js +2 -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/Switch/Switch.d.ts +16 -0
- package/dist/types/src/Switch/Switch.styles.d.ts +18 -0
- package/dist/types/src/Switch/components/BaseSwitch/BaseSwitch.d.ts +28 -0
- package/dist/types/src/Switch/components/BaseSwitch/index.d.ts +2 -0
- package/dist/types/src/Switch/index.d.ts +1 -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 +2 -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/Switch/Switch.styles.ts +21 -0
- package/src/Switch/Switch.test.tsx +98 -0
- package/src/Switch/Switch.tsx +58 -0
- package/src/Switch/components/BaseSwitch/BaseSwitch.test.tsx +62 -0
- package/src/Switch/components/BaseSwitch/BaseSwitch.tsx +107 -0
- package/src/Switch/components/BaseSwitch/__snapshots__/BaseSwitch.test.tsx.snap +217 -0
- package/src/Switch/components/BaseSwitch/index.ts +2 -0
- package/src/Switch/index.ts +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useFormController.ts +68 -0
- package/src/index.ts +2 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState, } from "react";
|
|
2
|
+
import { Platform, TextInput, } from "react-native";
|
|
3
|
+
import identity from "lodash.identity";
|
|
4
|
+
import { styles } from "./InputText.style";
|
|
5
|
+
import { useInputAccessoriesContext } from "./context";
|
|
6
|
+
import { useFormController } from "../hooks";
|
|
7
|
+
import { InputFieldWrapper, useShowClear, } from "../InputFieldWrapper";
|
|
8
|
+
import { commonInputStyles } from "../InputFieldWrapper/CommonInputStyles.style";
|
|
9
|
+
export const InputText = forwardRef(InputTextInternal);
|
|
10
|
+
// eslint-disable-next-line max-statements
|
|
11
|
+
function InputTextInternal({ invalid, disabled, name, placeholder, assistiveText, keyboard, value: controlledValue, defaultValue, autoFocus, autoComplete = "off", spellCheck, textContentType = "none", validations, onChangeText, onSubmitEditing, onFocus, accessibilityLabel, accessibilityHint, autoCorrect, autoCapitalize, onBlur, multiline = false, prefix, suffix, transform = {}, clearable = multiline ? "never" : "while-editing", testID, secureTextEntry, styleOverride, }, ref) {
|
|
12
|
+
var _a;
|
|
13
|
+
const isAndroid = Platform.OS === "android";
|
|
14
|
+
const { input: inputTransform = identity, output: outputTransform = identity, } = transform;
|
|
15
|
+
const { error, field } = useFormController({
|
|
16
|
+
name,
|
|
17
|
+
value: controlledValue !== null && controlledValue !== void 0 ? controlledValue : defaultValue,
|
|
18
|
+
validations,
|
|
19
|
+
});
|
|
20
|
+
const internalValue = controlledValue !== null && controlledValue !== void 0 ? controlledValue : (_a = field.value) === null || _a === void 0 ? void 0 : _a.toString();
|
|
21
|
+
const hasValue = internalValue !== "" && internalValue !== undefined;
|
|
22
|
+
const [focused, setFocused] = useState(false);
|
|
23
|
+
const { hasMiniLabel, setHasMiniLabel } = useMiniLabel(internalValue);
|
|
24
|
+
const textInputRef = useTextInputRef({ ref, onClear: handleClear });
|
|
25
|
+
const showClear = useShowClear({
|
|
26
|
+
clearable,
|
|
27
|
+
multiline,
|
|
28
|
+
focused,
|
|
29
|
+
hasValue,
|
|
30
|
+
disabled,
|
|
31
|
+
});
|
|
32
|
+
// Android doesn't have an accessibility label like iOS does. By adding
|
|
33
|
+
// it as a placeholder it readds it like a label. However we don't want to
|
|
34
|
+
// add a placeholder on iOS.
|
|
35
|
+
const androidA11yProps = Object.assign({}, (isAndroid && {
|
|
36
|
+
placeholder: accessibilityLabel || placeholder,
|
|
37
|
+
placeholderTextColor: "transparent",
|
|
38
|
+
}));
|
|
39
|
+
const _name = name !== null && name !== void 0 ? name : field.name;
|
|
40
|
+
const { inputAccessoryID, register, unregister, setFocusedInput, canFocusNext, onFocusNext, } = useInputAccessoriesContext();
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
_name &&
|
|
43
|
+
register(_name, () => {
|
|
44
|
+
var _a;
|
|
45
|
+
(_a = textInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
46
|
+
});
|
|
47
|
+
return () => {
|
|
48
|
+
_name && unregister(_name);
|
|
49
|
+
};
|
|
50
|
+
}, [_name, register, textInputRef, unregister]);
|
|
51
|
+
const returnKeyType = useMemo(() => {
|
|
52
|
+
if (!multiline) {
|
|
53
|
+
if (inputAccessoryID && isAndroid) {
|
|
54
|
+
return "next";
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
return "done";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}, [multiline, inputAccessoryID, isAndroid]);
|
|
64
|
+
// If it's not inside an inputAcessoriesContext or cannot focus next,
|
|
65
|
+
// then hide the keyboard when the return key is pressed.
|
|
66
|
+
const shouldBlurOnSubmit = !multiline && (!inputAccessoryID || !canFocusNext || !isAndroid);
|
|
67
|
+
function handleOnFocusNext() {
|
|
68
|
+
if (multiline) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
onFocusNext();
|
|
72
|
+
}
|
|
73
|
+
return (React.createElement(InputFieldWrapper, { prefix: prefix, suffix: suffix, hasValue: hasValue, hasMiniLabel: hasMiniLabel, assistiveText: assistiveText, focused: focused, error: error, invalid: invalid, placeholder: placeholder, disabled: disabled, onClear: handleClear, showClearAction: showClear, styleOverride: styleOverride },
|
|
74
|
+
React.createElement(TextInput, Object.assign({ inputAccessoryViewID: inputAccessoryID || undefined, testID: testID, autoCapitalize: autoCapitalize, autoCorrect: autoCorrect, spellCheck: spellCheck, style: [
|
|
75
|
+
commonInputStyles.input,
|
|
76
|
+
styles.inputPaddingTop,
|
|
77
|
+
!hasMiniLabel && commonInputStyles.inputEmpty,
|
|
78
|
+
disabled && commonInputStyles.inputDisabled,
|
|
79
|
+
multiline && Platform.OS === "ios" && styles.multilineInputiOS,
|
|
80
|
+
multiline && styles.multiLineInput,
|
|
81
|
+
multiline && hasMiniLabel && styles.multiLineInputWithMini,
|
|
82
|
+
styleOverride === null || styleOverride === void 0 ? void 0 : styleOverride.inputText,
|
|
83
|
+
], editable: !disabled, keyboardType: keyboard, value: inputTransform(internalValue), autoFocus: autoFocus, autoComplete: autoComplete, multiline: multiline, scrollEnabled: false, textContentType: textContentType, onChangeText: handleChangeText, onSubmitEditing: handleOnSubmitEditing, returnKeyType: returnKeyType, blurOnSubmit: shouldBlurOnSubmit, accessibilityLabel: accessibilityLabel || placeholder, accessibilityHint: accessibilityHint, secureTextEntry: secureTextEntry }, androidA11yProps, { onFocus: event => {
|
|
84
|
+
_name && setFocusedInput(_name);
|
|
85
|
+
setFocused(true);
|
|
86
|
+
onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
|
|
87
|
+
}, onBlur: () => {
|
|
88
|
+
_name && setFocusedInput("");
|
|
89
|
+
setFocused(false);
|
|
90
|
+
onBlur === null || onBlur === void 0 ? void 0 : onBlur();
|
|
91
|
+
field.onBlur();
|
|
92
|
+
trimWhitespace(field, onChangeText);
|
|
93
|
+
}, ref: (instance) => {
|
|
94
|
+
// RHF wants us to do it this way
|
|
95
|
+
// https://react-hook-form.com/faqs#Howtosharerefusage
|
|
96
|
+
textInputRef.current = instance;
|
|
97
|
+
field.ref(instance);
|
|
98
|
+
} }))));
|
|
99
|
+
function handleChangeText(value) {
|
|
100
|
+
const newValue = outputTransform(value);
|
|
101
|
+
setHasMiniLabel(Boolean(newValue));
|
|
102
|
+
onChangeText === null || onChangeText === void 0 ? void 0 : onChangeText(newValue);
|
|
103
|
+
field.onChange(newValue);
|
|
104
|
+
}
|
|
105
|
+
function handleClear() {
|
|
106
|
+
handleChangeText("");
|
|
107
|
+
}
|
|
108
|
+
function handleOnSubmitEditing() {
|
|
109
|
+
onSubmitEditing === null || onSubmitEditing === void 0 ? void 0 : onSubmitEditing();
|
|
110
|
+
if (isAndroid) {
|
|
111
|
+
handleOnFocusNext();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function trimWhitespace(field, onChangeText) {
|
|
116
|
+
if (!field.value || !field.value.trim) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const trimmedInput = field.value.trim();
|
|
120
|
+
onChangeText === null || onChangeText === void 0 ? void 0 : onChangeText(trimmedInput);
|
|
121
|
+
field.onChange(trimmedInput);
|
|
122
|
+
}
|
|
123
|
+
function useTextInputRef({ ref, onClear }) {
|
|
124
|
+
const textInputRef = useRef(null);
|
|
125
|
+
useImperativeHandle(ref, () => ({
|
|
126
|
+
focus: () => { var _a; return (_a = textInputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); },
|
|
127
|
+
blur: () => { var _a; return (_a = textInputRef.current) === null || _a === void 0 ? void 0 : _a.blur(); },
|
|
128
|
+
clear: onClear,
|
|
129
|
+
}), [onClear]);
|
|
130
|
+
return textInputRef;
|
|
131
|
+
}
|
|
132
|
+
function useMiniLabel(internalValue) {
|
|
133
|
+
const [hasMiniLabel, setHasMiniLabel] = useState(Boolean(internalValue));
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
setHasMiniLabel(Boolean(internalValue));
|
|
136
|
+
}, [internalValue]);
|
|
137
|
+
return { hasMiniLabel, setHasMiniLabel };
|
|
138
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
|
+
import { tokens } from "../utils/design";
|
|
3
|
+
import { typographyStyles } from "../Typography";
|
|
4
|
+
export const styles = StyleSheet.create({
|
|
5
|
+
inputPaddingTop: {
|
|
6
|
+
paddingTop: typographyStyles.smallSize.fontSize,
|
|
7
|
+
},
|
|
8
|
+
multiLineInput: {
|
|
9
|
+
paddingTop: tokens["space-base"] - tokens["space-smallest"],
|
|
10
|
+
paddingBottom: tokens["space-smaller"],
|
|
11
|
+
lineHeight: typographyStyles.defaultSize.lineHeight,
|
|
12
|
+
},
|
|
13
|
+
multiLineInputWithMini: {
|
|
14
|
+
paddingTop: tokens["space-large"],
|
|
15
|
+
},
|
|
16
|
+
multilineInputiOS: {
|
|
17
|
+
// for placeholder
|
|
18
|
+
paddingTop: (typographyStyles.smallSize.fontSize || 0) + tokens["space-smallest"],
|
|
19
|
+
},
|
|
20
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
const defaultValues = {
|
|
3
|
+
elements: {},
|
|
4
|
+
focusedInput: "",
|
|
5
|
+
canFocusNext: false,
|
|
6
|
+
canFocusPrevious: false,
|
|
7
|
+
inputAccessoryID: undefined,
|
|
8
|
+
register: () => undefined,
|
|
9
|
+
unregister: () => undefined,
|
|
10
|
+
onFocusNext: () => undefined,
|
|
11
|
+
onFocusPrevious: () => undefined,
|
|
12
|
+
setFocusedInput: () => undefined,
|
|
13
|
+
};
|
|
14
|
+
export const InputAccessoriesContext = createContext(defaultValues);
|
|
15
|
+
export function useInputAccessoriesContext() {
|
|
16
|
+
return useContext(InputAccessoriesContext);
|
|
17
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { InputAccessoryView, Keyboard, Platform,
|
|
3
|
+
// eslint-disable-next-line no-restricted-imports
|
|
4
|
+
Button as RNButton, View, useColorScheme, } from "react-native";
|
|
5
|
+
import { v4 } from "react-native-uuid";
|
|
6
|
+
import { InputAccessoriesContext } from "./InputAccessoriesContext";
|
|
7
|
+
import { styles } from "./InputAccessoriesProvider.style";
|
|
8
|
+
export function InputAccessoriesProvider({ children, }) {
|
|
9
|
+
const inputAccessoryID = useRef(v4()).current;
|
|
10
|
+
const { focusedInput, setFocusedInput, canFocusNext, canFocusPrevious, elements, setElements, previousKey, nextKey, } = useInputAccessoriesProviderState();
|
|
11
|
+
const colorScheme = useColorScheme();
|
|
12
|
+
return (React.createElement(InputAccessoriesContext.Provider, { value: {
|
|
13
|
+
elements,
|
|
14
|
+
focusedInput,
|
|
15
|
+
canFocusNext,
|
|
16
|
+
canFocusPrevious,
|
|
17
|
+
inputAccessoryID,
|
|
18
|
+
register,
|
|
19
|
+
unregister,
|
|
20
|
+
onFocusNext,
|
|
21
|
+
onFocusPrevious,
|
|
22
|
+
setFocusedInput,
|
|
23
|
+
} },
|
|
24
|
+
children,
|
|
25
|
+
Platform.OS === "ios" && (React.createElement(InputAccessoryView, { nativeID: inputAccessoryID },
|
|
26
|
+
React.createElement(View, { testID: "ATL-InputAccessory", style: [
|
|
27
|
+
styles.container,
|
|
28
|
+
colorScheme === "dark" ? styles.darkTheme : styles.lightTheme,
|
|
29
|
+
] },
|
|
30
|
+
React.createElement(RNButton, { onPress: Keyboard.dismiss, title: "Done", testID: "ATL-InputAccessory-Done", color: colorScheme === "dark" ? "white" : undefined }))))));
|
|
31
|
+
function register(name, onFocus) {
|
|
32
|
+
elements[name] = onFocus;
|
|
33
|
+
setElements(elements);
|
|
34
|
+
}
|
|
35
|
+
function unregister(name) {
|
|
36
|
+
delete elements[name];
|
|
37
|
+
setElements(elements);
|
|
38
|
+
}
|
|
39
|
+
function onFocusNext() {
|
|
40
|
+
const nextElement = elements[nextKey];
|
|
41
|
+
nextElement === null || nextElement === void 0 ? void 0 : nextElement();
|
|
42
|
+
}
|
|
43
|
+
function onFocusPrevious() {
|
|
44
|
+
const previousElement = elements[previousKey];
|
|
45
|
+
previousElement === null || previousElement === void 0 ? void 0 : previousElement();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function useInputAccessoriesProviderState() {
|
|
49
|
+
const [focusedInput, setFocusedInput] = useState("");
|
|
50
|
+
const [canFocusNext, setCanFocusNext] = useState(false);
|
|
51
|
+
const [canFocusPrevious, setCanFocusPrevious] = useState(false);
|
|
52
|
+
const [elements, setElements] = useState({});
|
|
53
|
+
const keys = Object.keys(elements);
|
|
54
|
+
const selectedIndex = keys.findIndex(key => key === focusedInput);
|
|
55
|
+
const nextKey = keys[selectedIndex + 1];
|
|
56
|
+
const previousKey = keys[selectedIndex - 1];
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
setCanFocusNext(Boolean(nextKey));
|
|
59
|
+
setCanFocusPrevious(Boolean(previousKey));
|
|
60
|
+
}, [previousKey, nextKey]);
|
|
61
|
+
return {
|
|
62
|
+
previousKey,
|
|
63
|
+
nextKey,
|
|
64
|
+
focusedInput,
|
|
65
|
+
setFocusedInput,
|
|
66
|
+
canFocusNext,
|
|
67
|
+
setCanFocusNext,
|
|
68
|
+
canFocusPrevious,
|
|
69
|
+
setCanFocusPrevious,
|
|
70
|
+
elements,
|
|
71
|
+
setElements,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PlatformColor, StyleSheet } from "react-native";
|
|
2
|
+
import { tokens } from "../../utils/design";
|
|
3
|
+
const BAR_HEIGHT = 44;
|
|
4
|
+
export const styles = StyleSheet.create({
|
|
5
|
+
container: {
|
|
6
|
+
flexDirection: "row",
|
|
7
|
+
justifyContent: "flex-end",
|
|
8
|
+
alignItems: "center",
|
|
9
|
+
paddingHorizontal: tokens["space-small"],
|
|
10
|
+
borderTopWidth: tokens["space-minuscule"],
|
|
11
|
+
borderTopColor: tokens["color-border"],
|
|
12
|
+
height: BAR_HEIGHT,
|
|
13
|
+
},
|
|
14
|
+
lightTheme: {
|
|
15
|
+
backgroundColor: tokens["color-surface--background"],
|
|
16
|
+
},
|
|
17
|
+
darkTheme: {
|
|
18
|
+
// PlatformColor has to be conditional for Storybook to run without error
|
|
19
|
+
backgroundColor: PlatformColor === null || PlatformColor === void 0 ? void 0 : PlatformColor("systemGray3"),
|
|
20
|
+
},
|
|
21
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
|
+
import { tokens } from "../../utils/design";
|
|
3
|
+
export const styles = StyleSheet.create({
|
|
4
|
+
container: {
|
|
5
|
+
flexDirection: "row",
|
|
6
|
+
justifyContent: "space-between",
|
|
7
|
+
alignItems: "center",
|
|
8
|
+
paddingHorizontal: tokens["space-small"],
|
|
9
|
+
backgroundColor: tokens["color-surface--background"],
|
|
10
|
+
borderTopWidth: tokens["space-minuscule"],
|
|
11
|
+
borderTopColor: tokens["color-border"],
|
|
12
|
+
},
|
|
13
|
+
buttonContainer: {
|
|
14
|
+
flexDirection: "row",
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { BaseSwitch } from "./components/BaseSwitch";
|
|
4
|
+
import { styles } from "./Switch.styles";
|
|
5
|
+
import { Text } from "../Text";
|
|
6
|
+
export function Switch(props) {
|
|
7
|
+
const switchProps = Object.assign(Object.assign({}, props), { accessibilityLabel: props.accessibilityLabel || props.label });
|
|
8
|
+
const [labelWidth, setLabelWidth] = useState();
|
|
9
|
+
return (React.createElement(View, { style: styles.container },
|
|
10
|
+
React.createElement(View, { style: styles.row },
|
|
11
|
+
props.label && (React.createElement(View, { style: styles.label, onLayout: event => setLabelWidth(event.nativeEvent.layout.width), testID: "switch-label-view" },
|
|
12
|
+
React.createElement(Text, { variation: props.disabled ? "disabled" : "base" }, props.label))),
|
|
13
|
+
React.createElement(BaseSwitch, Object.assign({}, switchProps))),
|
|
14
|
+
props.description && (React.createElement(View, { style: [styles.description, { maxWidth: labelWidth }], testID: "switch-description-view" },
|
|
15
|
+
React.createElement(Text, { level: "textSupporting", variation: "subdued" }, props.description)))));
|
|
16
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
|
+
import { tokens } from "../utils/design";
|
|
3
|
+
export const styles = StyleSheet.create({
|
|
4
|
+
container: {
|
|
5
|
+
marginTop: tokens["space-base"],
|
|
6
|
+
marginBottom: tokens["space-base"],
|
|
7
|
+
},
|
|
8
|
+
row: {
|
|
9
|
+
flexDirection: "row",
|
|
10
|
+
width: "100%",
|
|
11
|
+
},
|
|
12
|
+
label: {
|
|
13
|
+
flex: 1,
|
|
14
|
+
justifyContent: "center",
|
|
15
|
+
marginRight: tokens["space-small"],
|
|
16
|
+
},
|
|
17
|
+
description: {
|
|
18
|
+
marginTop: tokens["space-smaller"],
|
|
19
|
+
},
|
|
20
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Platform } from "react-native";
|
|
3
|
+
import { Switch } from "react-native-gesture-handler";
|
|
4
|
+
import { useFormController } from "../../../hooks";
|
|
5
|
+
import { tokens } from "../../../utils/design";
|
|
6
|
+
export function BaseSwitch({ value, defaultValue, onValueChange, disabled = false, accessibilityLabel, name, }) {
|
|
7
|
+
const { field } = useFormController({
|
|
8
|
+
name,
|
|
9
|
+
value: value !== null && value !== void 0 ? value : defaultValue,
|
|
10
|
+
});
|
|
11
|
+
const internalValue = value !== null && value !== void 0 ? value : field.value;
|
|
12
|
+
function getThumbColor() {
|
|
13
|
+
if (Platform.OS === "android") {
|
|
14
|
+
if (disabled) {
|
|
15
|
+
return tokens["color-disabled"];
|
|
16
|
+
}
|
|
17
|
+
else if (internalValue) {
|
|
18
|
+
return tokens["color-interactive"];
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
return tokens["color-surface--background"];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return undefined; //use default iOS
|
|
25
|
+
}
|
|
26
|
+
function getTrackColors() {
|
|
27
|
+
if (Platform.OS === "android") {
|
|
28
|
+
return {
|
|
29
|
+
true: disabled
|
|
30
|
+
? tokens["color-disabled--secondary"]
|
|
31
|
+
: tokens["color-green--lighter"],
|
|
32
|
+
false: disabled
|
|
33
|
+
? tokens["color-disabled--secondary"]
|
|
34
|
+
: tokens["color-disabled"],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
//iOS
|
|
39
|
+
return {
|
|
40
|
+
true: tokens["color-interactive"],
|
|
41
|
+
false: tokens["color-surface--background"],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return (React.createElement(Switch, { value: internalValue, onValueChange: (val) => {
|
|
46
|
+
if (!disabled) {
|
|
47
|
+
onValueChange === null || onValueChange === void 0 ? void 0 : onValueChange(val);
|
|
48
|
+
field.onChange(val);
|
|
49
|
+
}
|
|
50
|
+
}, disabled: disabled, thumbColor: getThumbColor(), trackColor: getTrackColors(), ios_backgroundColor: tokens["color-surface--background"], accessibilityLabel: accessibilityLabel, accessibilityRole: "switch", accessibilityState: {
|
|
51
|
+
disabled: disabled,
|
|
52
|
+
checked: internalValue,
|
|
53
|
+
} }));
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { BaseSwitch } from "./BaseSwitch";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Switch } from "./Switch";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useFormController";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { v1 } from "react-native-uuid";
|
|
2
|
+
import { useController, useForm, useFormContext, } from "react-hook-form";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
export function useFormController({ name, value, validations, }) {
|
|
5
|
+
var _a, _b;
|
|
6
|
+
const fieldName = useControlName(name);
|
|
7
|
+
const form = useForm({
|
|
8
|
+
mode: "onTouched",
|
|
9
|
+
defaultValues: { [fieldName]: value },
|
|
10
|
+
});
|
|
11
|
+
const formContext = useFormContext();
|
|
12
|
+
const { control, formState: { errors }, } = formContext || form;
|
|
13
|
+
const { field } = useController({
|
|
14
|
+
name: fieldName,
|
|
15
|
+
control,
|
|
16
|
+
rules: validations,
|
|
17
|
+
defaultValue: value || undefined,
|
|
18
|
+
});
|
|
19
|
+
// The naming convention established by react-hook-form for arrays of fields is, for example, "emails.0.description".
|
|
20
|
+
// This corresponds to the structure of the error object, e.g. { emails: { 0: { description: "foobar" } } }
|
|
21
|
+
// 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").
|
|
22
|
+
// TODO: Add support for two-part identifiers (e.g. "property.province")
|
|
23
|
+
const fieldIdentifiers = fieldName.split(".");
|
|
24
|
+
let error;
|
|
25
|
+
if (fieldIdentifiers.length === 3) {
|
|
26
|
+
const [section, item, identifier] = fieldIdentifiers;
|
|
27
|
+
error = (_b = (_a = errors[section]) === null || _a === void 0 ? void 0 : _a[item]) === null || _b === void 0 ? void 0 : _b[identifier];
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
error = errors[fieldName];
|
|
31
|
+
}
|
|
32
|
+
return { error, field };
|
|
33
|
+
}
|
|
34
|
+
function useControlName(name) {
|
|
35
|
+
const [identifier] = useState(v1());
|
|
36
|
+
const prefix = `generatedName--${identifier}`;
|
|
37
|
+
return `${name || prefix}`;
|
|
38
|
+
}
|
package/dist/src/index.js
CHANGED
|
@@ -15,7 +15,9 @@ export * from "./Icon";
|
|
|
15
15
|
export * from "./IconButton";
|
|
16
16
|
export * from "./InputFieldWrapper";
|
|
17
17
|
export * from "./InputPressable";
|
|
18
|
+
export * from "./InputText";
|
|
18
19
|
export * from "./ProgressBar";
|
|
19
20
|
export * from "./StatusLabel";
|
|
21
|
+
export * from "./Switch";
|
|
20
22
|
export * from "./Text";
|
|
21
23
|
export * from "./Typography";
|