ferns-ui 1.16.1 → 2.0.0-beta.2
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/Accordion.js +2 -7
- package/dist/Accordion.js.map +1 -1
- package/dist/ActionSheet.js +11 -14
- package/dist/ActionSheet.js.map +1 -1
- package/dist/AddressField.js +1 -1
- package/dist/AddressField.js.map +1 -1
- package/dist/Badge.js +1 -1
- package/dist/Badge.js.map +1 -1
- package/dist/Banner.js +1 -1
- package/dist/Banner.js.map +1 -1
- package/dist/Box.js +3 -3
- package/dist/Box.js.map +1 -1
- package/dist/Button.js +1 -0
- package/dist/Button.js.map +1 -1
- package/dist/CheckBox.js.map +1 -1
- package/dist/Common.d.ts +9 -13
- package/dist/Common.js.map +1 -1
- package/dist/DataTable.js +2 -1
- package/dist/DataTable.js.map +1 -1
- package/dist/DateTimeField.js +22 -22
- package/dist/DateTimeField.js.map +1 -1
- package/dist/EmailField.js +37 -17
- package/dist/EmailField.js.map +1 -1
- package/dist/ErrorBoundary.d.ts +1 -1
- package/dist/FernsProvider.js +1 -1
- package/dist/FernsProvider.js.map +1 -1
- package/dist/Heading.js +1 -3
- package/dist/Heading.js.map +1 -1
- package/dist/Hyperlink.js +1 -1
- package/dist/Hyperlink.js.map +1 -1
- package/dist/IconButton.js +1 -1
- package/dist/IconButton.js.map +1 -1
- package/dist/Image.js.map +1 -1
- package/dist/MobileAddressAutoComplete.js +1 -1
- package/dist/MobileAddressAutoComplete.js.map +1 -1
- package/dist/Modal.d.ts +1 -1
- package/dist/Modal.js +15 -35
- package/dist/Modal.js.map +1 -1
- package/dist/ModalSheet.d.ts +1 -1
- package/dist/ModalSheet.js +1 -1
- package/dist/ModalSheet.js.map +1 -1
- package/dist/NumberField.js +4 -10
- package/dist/NumberField.js.map +1 -1
- package/dist/NumberPickerActionSheet.d.ts +3 -1
- package/dist/NumberPickerActionSheet.js +3 -0
- package/dist/NumberPickerActionSheet.js.map +1 -1
- package/dist/Page.js +1 -1
- package/dist/Page.js.map +1 -1
- package/dist/Pagination.js +2 -2
- package/dist/Pagination.js.map +1 -1
- package/dist/Permissions.d.ts +1 -1
- package/dist/Permissions.js +2 -2
- package/dist/Permissions.js.map +1 -1
- package/dist/PickerSelect.js +1 -1
- package/dist/PickerSelect.js.map +1 -1
- package/dist/SectionDivider.js +1 -1
- package/dist/SectionDivider.js.map +1 -1
- package/dist/SegmentedControl.js.map +1 -1
- package/dist/Signature.native.js +2 -2
- package/dist/Signature.native.js.map +1 -1
- package/dist/SignatureField.js +2 -2
- package/dist/SignatureField.js.map +1 -1
- package/dist/Slider.js +3 -3
- package/dist/Slider.js.map +1 -1
- package/dist/SplitPage.js +7 -7
- package/dist/SplitPage.js.map +1 -1
- package/dist/SplitPage.native.js +6 -4
- package/dist/SplitPage.native.js.map +1 -1
- package/dist/TapToEdit.js +3 -3
- package/dist/TapToEdit.js.map +1 -1
- package/dist/Text.js +1 -1
- package/dist/Text.js.map +1 -1
- package/dist/TextFieldNumberActionSheet.d.ts +4 -2
- package/dist/TextFieldNumberActionSheet.js +4 -1
- package/dist/TextFieldNumberActionSheet.js.map +1 -1
- package/dist/Tooltip.js +21 -39
- package/dist/Tooltip.js.map +1 -1
- package/dist/Unifier.d.ts +1 -0
- package/dist/Unifier.js.map +1 -1
- package/dist/Utilities.d.ts +1 -1
- package/dist/Utilities.js +3 -2
- package/dist/Utilities.js.map +1 -1
- package/dist/WebAddressAutocomplete.js +1 -2
- package/dist/WebAddressAutocomplete.js.map +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.js +9 -10
- package/dist/index.js.map +1 -1
- package/dist/jestSetup.d.ts +0 -0
- package/dist/jestSetup.js +21 -0
- package/dist/jestSetup.js.map +1 -0
- package/dist/setupTests.js +22 -0
- package/dist/setupTests.js.map +1 -1
- package/dist/table/Table.js +15 -14
- package/dist/table/Table.js.map +1 -1
- package/dist/table/TableHeaderCell.js +2 -2
- package/dist/table/TableHeaderCell.js.map +1 -1
- package/dist/test-utils.d.ts +4 -1
- package/dist/useStoredState.js +2 -4
- package/dist/useStoredState.js.map +1 -1
- package/package.json +106 -43
- package/src/Accordion.tsx +1 -7
- package/src/ActionSheet.tsx +22 -26
- package/src/AddressField.tsx +1 -1
- package/src/Avatar.test.tsx +0 -2
- package/src/Badge.tsx +1 -1
- package/src/Banner.tsx +1 -1
- package/src/Box.test.tsx +70 -71
- package/src/Box.tsx +9 -21
- package/src/Button.tsx +1 -0
- package/src/CheckBox.tsx +1 -7
- package/src/Common.ts +21 -14
- package/src/DataTable.tsx +4 -3
- package/src/DateTimeField.tsx +22 -22
- package/src/EmailField.tsx +42 -22
- package/src/FernsProvider.tsx +4 -1
- package/src/Heading.tsx +1 -3
- package/src/Hyperlink.tsx +2 -2
- package/src/IconButton.tsx +2 -2
- package/src/Image.tsx +0 -1
- package/src/MobileAddressAutoComplete.tsx +1 -1
- package/src/Modal.tsx +21 -58
- package/src/ModalSheet.tsx +1 -1
- package/src/NumberField.tsx +4 -10
- package/src/NumberPickerActionSheet.tsx +5 -1
- package/src/Page.tsx +1 -1
- package/src/Pagination.tsx +11 -2
- package/src/Permissions.ts +2 -2
- package/src/PickerSelect.tsx +1 -1
- package/src/SectionDivider.tsx +1 -1
- package/src/SegmentedControl.tsx +1 -3
- package/src/Signature.native.tsx +2 -2
- package/src/SignatureField.tsx +2 -2
- package/src/Slider.tsx +17 -10
- package/src/SplitPage.native.tsx +4 -2
- package/src/SplitPage.tsx +4 -4
- package/src/TapToEdit.tsx +7 -3
- package/src/Text.tsx +1 -1
- package/src/TextArea.test.tsx +43 -27
- package/src/TextField.test.tsx +4 -3
- package/src/TextFieldNumberActionSheet.tsx +7 -3
- package/src/Tooltip.tsx +29 -45
- package/src/Unifier.ts +3 -1
- package/src/Utilities.tsx +4 -3
- package/src/WebAddressAutocomplete.tsx +1 -1
- package/src/index.tsx +10 -11
- package/src/jestSetup.ts +16 -0
- package/src/setupTests.ts +24 -0
- package/src/table/Table.tsx +38 -36
- package/src/table/TableHeaderCell.tsx +2 -2
- package/src/useStoredState.ts +11 -13
- package/dist/Accordion.test.d.ts +0 -1
- package/dist/Accordion.test.js +0 -71
- package/dist/Accordion.test.js.map +0 -1
- package/dist/AddressField.test.d.ts +0 -1
- package/dist/AddressField.test.js +0 -65
- package/dist/AddressField.test.js.map +0 -1
- package/dist/Avatar.test.d.ts +0 -1
- package/dist/Avatar.test.js +0 -131
- package/dist/Avatar.test.js.map +0 -1
- package/dist/Badge.test.d.ts +0 -1
- package/dist/Badge.test.js +0 -76
- package/dist/Badge.test.js.map +0 -1
- package/dist/Box.test.d.ts +0 -1
- package/dist/Box.test.js +0 -528
- package/dist/Box.test.js.map +0 -1
- package/dist/DateTimeField.test.d.ts +0 -1
- package/dist/DateTimeField.test.js +0 -258
- package/dist/DateTimeField.test.js.map +0 -1
- package/dist/DateUtilities.test.d.ts +0 -1
- package/dist/DateUtilities.test.js +0 -279
- package/dist/DateUtilities.test.js.map +0 -1
- package/dist/MarkdownView.d.ts +0 -5
- package/dist/MarkdownView.js +0 -44
- package/dist/MarkdownView.js.map +0 -1
- package/dist/TextArea.test.d.ts +0 -1
- package/dist/TextArea.test.js +0 -146
- package/dist/TextArea.test.js.map +0 -1
- package/dist/TextField.test.d.ts +0 -1
- package/dist/TextField.test.js +0 -251
- package/dist/TextField.test.js.map +0 -1
- package/dist/useStoredState.test.d.ts +0 -1
- package/dist/useStoredState.test.js +0 -93
- package/dist/useStoredState.test.js.map +0 -1
- package/src/MarkdownView.tsx +0 -67
package/src/Slider.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import SliderComponent from "@react-native-community/slider";
|
|
2
2
|
import React, {FC} from "react";
|
|
3
|
+
import {View} from "react-native";
|
|
3
4
|
|
|
4
5
|
import {Box} from "./Box";
|
|
5
6
|
import {IconName, SliderProps, ValueMappingItem} from "./Common";
|
|
@@ -15,11 +16,11 @@ const getCurrentMapping = (map: ValueMappingItem[], value: number) => {
|
|
|
15
16
|
if (!map || map.length === 0) {
|
|
16
17
|
return null;
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
+
|
|
19
20
|
// Find the option with the closest value
|
|
20
21
|
let closestOption = map[0];
|
|
21
22
|
let closestDistance = Math.abs(value - closestOption.value);
|
|
22
|
-
|
|
23
|
+
|
|
23
24
|
for (const option of map) {
|
|
24
25
|
const distance = Math.abs(value - option.value);
|
|
25
26
|
if (distance < closestDistance) {
|
|
@@ -27,7 +28,7 @@ const getCurrentMapping = (map: ValueMappingItem[], value: number) => {
|
|
|
27
28
|
closestOption = option;
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
|
|
31
32
|
return closestOption;
|
|
32
33
|
};
|
|
33
34
|
|
|
@@ -48,9 +49,9 @@ const getCenterContent = (
|
|
|
48
49
|
</Text>
|
|
49
50
|
);
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
+
|
|
52
53
|
const currentOption = getCurrentMapping(valueMapping, value);
|
|
53
|
-
|
|
54
|
+
|
|
54
55
|
if (useIcons) {
|
|
55
56
|
return (
|
|
56
57
|
<Icon
|
|
@@ -60,7 +61,7 @@ const getCenterContent = (
|
|
|
60
61
|
/>
|
|
61
62
|
);
|
|
62
63
|
}
|
|
63
|
-
|
|
64
|
+
|
|
64
65
|
return (
|
|
65
66
|
<Text align="center" color={disabled ? "secondaryLight" : "primary"} size="2xl">
|
|
66
67
|
{currentOption?.label}
|
|
@@ -71,7 +72,7 @@ const getCenterContent = (
|
|
|
71
72
|
const getSliderContent = (
|
|
72
73
|
slider: React.ReactElement,
|
|
73
74
|
inlineLabels: boolean,
|
|
74
|
-
labels?: SliderProps[
|
|
75
|
+
labels?: SliderProps['labels']
|
|
75
76
|
): React.ReactElement => {
|
|
76
77
|
if (inlineLabels && labels?.min && labels?.max) {
|
|
77
78
|
return (
|
|
@@ -153,9 +154,9 @@ export const Slider: FC<SliderProps> = ({
|
|
|
153
154
|
thumbStyle: {
|
|
154
155
|
width: 48,
|
|
155
156
|
height: 48,
|
|
156
|
-
backgroundColor:
|
|
157
|
+
backgroundColor: 'white',
|
|
157
158
|
borderRadius: 24,
|
|
158
|
-
shadowColor:
|
|
159
|
+
shadowColor: '#000',
|
|
159
160
|
shadowOffset: {
|
|
160
161
|
width: 0,
|
|
161
162
|
height: 2,
|
|
@@ -188,7 +189,11 @@ export const Slider: FC<SliderProps> = ({
|
|
|
188
189
|
<Box>
|
|
189
190
|
{Boolean(title) && <FieldTitle text={title!} />}
|
|
190
191
|
<Box direction="column" gap={showSelection ? 2 : 0}>
|
|
191
|
-
{showSelection &&
|
|
192
|
+
{showSelection && (
|
|
193
|
+
<Box alignItems="center">
|
|
194
|
+
{centerContent}
|
|
195
|
+
</Box>
|
|
196
|
+
)}
|
|
192
197
|
{sliderContent}
|
|
193
198
|
</Box>
|
|
194
199
|
{Boolean(helperText && !errorText) && <FieldHelperText text={helperText!} />}
|
|
@@ -196,3 +201,5 @@ export const Slider: FC<SliderProps> = ({
|
|
|
196
201
|
</Box>
|
|
197
202
|
);
|
|
198
203
|
};
|
|
204
|
+
|
|
205
|
+
|
package/src/SplitPage.native.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import {SplitPageProps} from "./Common";
|
|
|
9
9
|
import {FlatList} from "./FlatList";
|
|
10
10
|
import {IconButton} from "./IconButton";
|
|
11
11
|
import {Spinner} from "./Spinner";
|
|
12
|
+
import {useTheme} from "./Theme";
|
|
12
13
|
import {Unifier} from "./Unifier";
|
|
13
14
|
|
|
14
15
|
export const SplitPage = ({
|
|
@@ -25,6 +26,7 @@ export const SplitPage = ({
|
|
|
25
26
|
bottomNavBarHeight,
|
|
26
27
|
showItemList,
|
|
27
28
|
}: SplitPageProps) => {
|
|
29
|
+
const {theme} = useTheme();
|
|
28
30
|
const [selectedId, setSelectedId] = useState<number | undefined>(undefined);
|
|
29
31
|
|
|
30
32
|
// flattenChildren is necessary to pull children from a React Fragment. Without this,
|
|
@@ -90,7 +92,7 @@ export const SplitPage = ({
|
|
|
90
92
|
paddingBottom: bottomNavBarHeight,
|
|
91
93
|
}}
|
|
92
94
|
>
|
|
93
|
-
{renderListViewHeader
|
|
95
|
+
{renderListViewHeader && renderListViewHeader()}
|
|
94
96
|
<FlatList
|
|
95
97
|
data={listViewData}
|
|
96
98
|
extraData={listViewExtraData}
|
|
@@ -115,7 +117,7 @@ export const SplitPage = ({
|
|
|
115
117
|
onClick={() => onItemDeselect()}
|
|
116
118
|
/>
|
|
117
119
|
</Box>
|
|
118
|
-
{renderContent
|
|
120
|
+
{renderContent && renderContent(selectedId)}
|
|
119
121
|
</Box>
|
|
120
122
|
);
|
|
121
123
|
};
|
package/src/SplitPage.tsx
CHANGED
|
@@ -95,7 +95,7 @@ export const SplitPage = ({
|
|
|
95
95
|
flexDirection: "column",
|
|
96
96
|
}}
|
|
97
97
|
>
|
|
98
|
-
{renderListViewHeader
|
|
98
|
+
{renderListViewHeader && renderListViewHeader()}
|
|
99
99
|
<FlatList
|
|
100
100
|
data={listViewData}
|
|
101
101
|
extraData={listViewExtraData}
|
|
@@ -109,7 +109,7 @@ export const SplitPage = ({
|
|
|
109
109
|
const renderListContent = () => {
|
|
110
110
|
return (
|
|
111
111
|
<Box flex="grow" padding={2}>
|
|
112
|
-
{renderContent
|
|
112
|
+
{renderContent && renderContent(selectedId)}
|
|
113
113
|
</Box>
|
|
114
114
|
);
|
|
115
115
|
};
|
|
@@ -205,7 +205,7 @@ export const SplitPage = ({
|
|
|
205
205
|
flexDirection: "column",
|
|
206
206
|
}}
|
|
207
207
|
>
|
|
208
|
-
{renderListViewHeader
|
|
208
|
+
{renderListViewHeader && renderListViewHeader()}
|
|
209
209
|
<FlatList
|
|
210
210
|
data={listViewData}
|
|
211
211
|
extraData={listViewExtraData}
|
|
@@ -234,7 +234,7 @@ export const SplitPage = ({
|
|
|
234
234
|
/>
|
|
235
235
|
</Box>
|
|
236
236
|
)}
|
|
237
|
-
{renderContent
|
|
237
|
+
{renderContent && renderContent(selectedId)}
|
|
238
238
|
</Box>
|
|
239
239
|
);
|
|
240
240
|
};
|
package/src/TapToEdit.tsx
CHANGED
|
@@ -13,7 +13,11 @@ const TapToEditTitle: FC<{
|
|
|
13
13
|
onlyShowHelperTextWhileEditing?: boolean;
|
|
14
14
|
title: string;
|
|
15
15
|
helperText?: string;
|
|
16
|
-
}> = ({
|
|
16
|
+
}> = ({
|
|
17
|
+
title,
|
|
18
|
+
helperText,
|
|
19
|
+
onlyShowHelperTextWhileEditing,
|
|
20
|
+
}) => {
|
|
17
21
|
return (
|
|
18
22
|
<View style={{flex: 1, justifyContent: "center"}}>
|
|
19
23
|
<Text bold>{title}</Text>
|
|
@@ -113,7 +117,7 @@ export const TapToEdit: FC<TapToEditProps> = ({
|
|
|
113
117
|
</View>
|
|
114
118
|
<View style={{gap: 16}}>
|
|
115
119
|
<Field
|
|
116
|
-
grow={fieldProps?.type === "textarea" ?
|
|
120
|
+
grow={fieldProps?.type === "textarea" ? fieldProps.grow ?? true : undefined}
|
|
117
121
|
helperText={helperText}
|
|
118
122
|
inputRef={
|
|
119
123
|
["text", "textarea", "url", "email", "number"].includes(fieldProps?.type)
|
|
@@ -206,7 +210,7 @@ export const TapToEdit: FC<TapToEditProps> = ({
|
|
|
206
210
|
try {
|
|
207
211
|
const url = new URL(value);
|
|
208
212
|
displayValue = url?.hostname ?? value;
|
|
209
|
-
} catch (
|
|
213
|
+
} catch (error) {
|
|
210
214
|
// Don't print an error message for empty values.
|
|
211
215
|
if (value) {
|
|
212
216
|
console.debug(`Invalid URL: $value`);
|
package/src/Text.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
useFonts,
|
|
9
9
|
} from "@expo-google-fonts/nunito";
|
|
10
10
|
import React from "react";
|
|
11
|
-
import {Text as NativeText,
|
|
11
|
+
import {Platform, Text as NativeText, TextStyle} from "react-native";
|
|
12
12
|
|
|
13
13
|
import {TextProps} from "./Common";
|
|
14
14
|
import {Hyperlink} from "./Hyperlink";
|
package/src/TextArea.test.tsx
CHANGED
|
@@ -20,7 +20,7 @@ describe("TextArea", () => {
|
|
|
20
20
|
const {getByDisplayValue} = renderWithTheme(
|
|
21
21
|
<TextArea value="test content" onChange={mockOnChange} />
|
|
22
22
|
);
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
const input = getByDisplayValue("test content");
|
|
25
25
|
expect(input.props.multiline).toBe(true);
|
|
26
26
|
expect(input.props.value).toBe("test content");
|
|
@@ -30,7 +30,7 @@ describe("TextArea", () => {
|
|
|
30
30
|
const {getByText} = renderWithTheme(
|
|
31
31
|
<TextArea title="Description" value="" onChange={mockOnChange} />
|
|
32
32
|
);
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
expect(getByText("Description")).toBeTruthy();
|
|
35
35
|
});
|
|
36
36
|
|
|
@@ -38,7 +38,7 @@ describe("TextArea", () => {
|
|
|
38
38
|
const {getByPlaceholderText} = renderWithTheme(
|
|
39
39
|
<TextArea placeholder="Enter description" value="" onChange={mockOnChange} />
|
|
40
40
|
);
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
expect(getByPlaceholderText("Enter description")).toBeTruthy();
|
|
43
43
|
});
|
|
44
44
|
|
|
@@ -46,7 +46,7 @@ describe("TextArea", () => {
|
|
|
46
46
|
const {getByText} = renderWithTheme(
|
|
47
47
|
<TextArea helperText="Maximum 500 characters" value="" onChange={mockOnChange} />
|
|
48
48
|
);
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
expect(getByText("Maximum 500 characters")).toBeTruthy();
|
|
51
51
|
});
|
|
52
52
|
|
|
@@ -54,7 +54,7 @@ describe("TextArea", () => {
|
|
|
54
54
|
const {getByText} = renderWithTheme(
|
|
55
55
|
<TextArea errorText="This field is required" value="" onChange={mockOnChange} />
|
|
56
56
|
);
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
expect(getByText("This field is required")).toBeTruthy();
|
|
59
59
|
});
|
|
60
60
|
|
|
@@ -62,7 +62,7 @@ describe("TextArea", () => {
|
|
|
62
62
|
const {getByDisplayValue} = renderWithTheme(
|
|
63
63
|
<TextArea grow value="" onChange={mockOnChange} />
|
|
64
64
|
);
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
const input = getByDisplayValue("");
|
|
67
67
|
expect(input.props.multiline).toBe(true);
|
|
68
68
|
});
|
|
@@ -71,14 +71,16 @@ describe("TextArea", () => {
|
|
|
71
71
|
const {getByDisplayValue} = renderWithTheme(
|
|
72
72
|
<TextArea disabled value="test" onChange={mockOnChange} />
|
|
73
73
|
);
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
const input = getByDisplayValue("test");
|
|
76
76
|
expect(input.props.readOnly).toBe(true);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
it("should always have text type", () => {
|
|
80
|
-
const {getByDisplayValue} = renderWithTheme(
|
|
81
|
-
|
|
80
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
81
|
+
<TextArea value="" onChange={mockOnChange} />
|
|
82
|
+
);
|
|
83
|
+
|
|
82
84
|
const input = getByDisplayValue("");
|
|
83
85
|
expect(input.props.keyboardType).toBe("default");
|
|
84
86
|
});
|
|
@@ -87,7 +89,9 @@ describe("TextArea", () => {
|
|
|
87
89
|
describe("user interactions", () => {
|
|
88
90
|
it("should call onChange when text is entered", async () => {
|
|
89
91
|
const user = userEvent.setup();
|
|
90
|
-
const {getByDisplayValue} = renderWithTheme(
|
|
92
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
93
|
+
<TextArea value="" onChange={mockOnChange} />
|
|
94
|
+
);
|
|
91
95
|
|
|
92
96
|
const input = getByDisplayValue("");
|
|
93
97
|
await user.type(input, "hello world");
|
|
@@ -99,7 +103,9 @@ describe("TextArea", () => {
|
|
|
99
103
|
it("should handle multiline text input", async () => {
|
|
100
104
|
const user = userEvent.setup();
|
|
101
105
|
const multilineText = "Line 1\nLine 2\nLine 3";
|
|
102
|
-
const {getByDisplayValue} = renderWithTheme(
|
|
106
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
107
|
+
<TextArea value="" onChange={mockOnChange} />
|
|
108
|
+
);
|
|
103
109
|
|
|
104
110
|
const input = getByDisplayValue("");
|
|
105
111
|
await user.type(input, multilineText);
|
|
@@ -111,8 +117,10 @@ describe("TextArea", () => {
|
|
|
111
117
|
|
|
112
118
|
describe("accessibility", () => {
|
|
113
119
|
it("should have correct accessibility properties", () => {
|
|
114
|
-
const {getByDisplayValue} = renderWithTheme(
|
|
115
|
-
|
|
120
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
121
|
+
<TextArea value="" onChange={mockOnChange} />
|
|
122
|
+
);
|
|
123
|
+
|
|
116
124
|
const input = getByDisplayValue("");
|
|
117
125
|
expect(input.props.accessibilityHint).toBe("Enter text here");
|
|
118
126
|
expect(input.props["aria-label"]).toBe("Text input field");
|
|
@@ -122,7 +130,7 @@ describe("TextArea", () => {
|
|
|
122
130
|
const {getByDisplayValue} = renderWithTheme(
|
|
123
131
|
<TextArea disabled value="" onChange={mockOnChange} />
|
|
124
132
|
);
|
|
125
|
-
|
|
133
|
+
|
|
126
134
|
const input = getByDisplayValue("");
|
|
127
135
|
expect(input.props.accessibilityState.disabled).toBe(true);
|
|
128
136
|
});
|
|
@@ -130,15 +138,19 @@ describe("TextArea", () => {
|
|
|
130
138
|
|
|
131
139
|
describe("edge cases", () => {
|
|
132
140
|
it("should handle empty value", () => {
|
|
133
|
-
const {getByDisplayValue} = renderWithTheme(
|
|
134
|
-
|
|
141
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
142
|
+
<TextArea value="" onChange={mockOnChange} />
|
|
143
|
+
);
|
|
144
|
+
|
|
135
145
|
const input = getByDisplayValue("");
|
|
136
146
|
expect(input.props.value).toBe("");
|
|
137
147
|
});
|
|
138
148
|
|
|
139
149
|
it("should handle undefined value", () => {
|
|
140
|
-
const {root} = renderWithTheme(
|
|
141
|
-
|
|
150
|
+
const {root} = renderWithTheme(
|
|
151
|
+
<TextArea value={undefined} onChange={mockOnChange} />
|
|
152
|
+
);
|
|
153
|
+
|
|
142
154
|
expect(root).toBeTruthy();
|
|
143
155
|
});
|
|
144
156
|
|
|
@@ -147,7 +159,7 @@ describe("TextArea", () => {
|
|
|
147
159
|
const {getByDisplayValue} = renderWithTheme(
|
|
148
160
|
<TextArea value={longText} onChange={mockOnChange} />
|
|
149
161
|
);
|
|
150
|
-
|
|
162
|
+
|
|
151
163
|
const input = getByDisplayValue(longText);
|
|
152
164
|
expect(input.props.value).toBe(longText);
|
|
153
165
|
});
|
|
@@ -157,7 +169,7 @@ describe("TextArea", () => {
|
|
|
157
169
|
const {getByDisplayValue} = renderWithTheme(
|
|
158
170
|
<TextArea value={textWithBreaks} onChange={mockOnChange} />
|
|
159
171
|
);
|
|
160
|
-
|
|
172
|
+
|
|
161
173
|
const input = getByDisplayValue(textWithBreaks);
|
|
162
174
|
expect(input.props.value).toBe(textWithBreaks);
|
|
163
175
|
});
|
|
@@ -167,10 +179,10 @@ describe("TextArea", () => {
|
|
|
167
179
|
it("should inherit all TextField props except multiline and type", () => {
|
|
168
180
|
const mockOnFocus = jest.fn();
|
|
169
181
|
const mockOnBlur = jest.fn();
|
|
170
|
-
|
|
182
|
+
|
|
171
183
|
const {getByDisplayValue} = renderWithTheme(
|
|
172
|
-
<TextArea
|
|
173
|
-
value="test"
|
|
184
|
+
<TextArea
|
|
185
|
+
value="test"
|
|
174
186
|
onChange={mockOnChange}
|
|
175
187
|
onFocus={mockOnFocus}
|
|
176
188
|
onBlur={mockOnBlur}
|
|
@@ -179,7 +191,7 @@ describe("TextArea", () => {
|
|
|
179
191
|
rows={5}
|
|
180
192
|
/>
|
|
181
193
|
);
|
|
182
|
-
|
|
194
|
+
|
|
183
195
|
const input = getByDisplayValue("test");
|
|
184
196
|
expect(input.props.numberOfLines).toBe(5);
|
|
185
197
|
expect(input.props.onFocus).toBeTruthy();
|
|
@@ -188,15 +200,19 @@ describe("TextArea", () => {
|
|
|
188
200
|
|
|
189
201
|
it("should support inputRef", () => {
|
|
190
202
|
const mockInputRef = jest.fn();
|
|
191
|
-
renderWithTheme(
|
|
192
|
-
|
|
203
|
+
renderWithTheme(
|
|
204
|
+
<TextArea inputRef={mockInputRef} value="" onChange={mockOnChange} />
|
|
205
|
+
);
|
|
206
|
+
|
|
193
207
|
expect(mockInputRef).toHaveBeenCalled();
|
|
194
208
|
});
|
|
195
209
|
});
|
|
196
210
|
|
|
197
211
|
describe("snapshots", () => {
|
|
198
212
|
it("should match snapshot with default props", () => {
|
|
199
|
-
const component = renderWithTheme(
|
|
213
|
+
const component = renderWithTheme(
|
|
214
|
+
<TextArea value="test content" onChange={mockOnChange} />
|
|
215
|
+
);
|
|
200
216
|
expect(component.toJSON()).toMatchSnapshot();
|
|
201
217
|
});
|
|
202
218
|
|
package/src/TextField.test.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {act, userEvent} from "@testing-library/react-native";
|
|
2
2
|
import React from "react";
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
import {renderWithTheme} from "./test-utils";
|
|
5
|
+
import {TextField} from "./TextField";
|
|
5
6
|
|
|
6
7
|
describe("TextField", () => {
|
|
7
8
|
let mockOnChange: jest.Mock;
|
|
@@ -102,7 +103,7 @@ describe("TextField", () => {
|
|
|
102
103
|
});
|
|
103
104
|
|
|
104
105
|
it("should call onEnter when enter key is pressed", async () => {
|
|
105
|
-
const
|
|
106
|
+
const user = userEvent.setup();
|
|
106
107
|
const {getByDisplayValue} = renderWithTheme(
|
|
107
108
|
<TextField value="" onChange={mockOnChange} onEnter={mockOnEnter} />
|
|
108
109
|
);
|
|
@@ -205,7 +206,7 @@ describe("TextField", () => {
|
|
|
205
206
|
});
|
|
206
207
|
|
|
207
208
|
it("should not call onFocus when disabled", async () => {
|
|
208
|
-
const
|
|
209
|
+
const user = userEvent.setup();
|
|
209
210
|
const {getByDisplayValue} = renderWithTheme(
|
|
210
211
|
<TextField disabled value="" onChange={mockOnChange} onFocus={mockOnFocus} />
|
|
211
212
|
);
|
|
@@ -4,14 +4,18 @@ import React from "react";
|
|
|
4
4
|
import {ActionSheet} from "./ActionSheet";
|
|
5
5
|
import {Box} from "./Box";
|
|
6
6
|
import {Button} from "./Button";
|
|
7
|
-
import {TextFieldPickerActionSheetProps} from "./Common";
|
|
7
|
+
import {NumberPickerActionSheetProps, TextFieldPickerActionSheetProps} from "./Common";
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
interface NumberPickerActionSheetState {}
|
|
10
10
|
|
|
11
11
|
export class NumberPickerActionSheet extends React.Component<
|
|
12
12
|
TextFieldPickerActionSheetProps,
|
|
13
13
|
NumberPickerActionSheetState
|
|
14
14
|
> {
|
|
15
|
+
constructor(props: NumberPickerActionSheetProps) {
|
|
16
|
+
super(props);
|
|
17
|
+
}
|
|
18
|
+
|
|
15
19
|
render() {
|
|
16
20
|
return (
|
|
17
21
|
<ActionSheet ref={this.props.actionSheetRef} bounceOnOpen gestureEnabled>
|
|
@@ -32,7 +36,7 @@ export class NumberPickerActionSheet extends React.Component<
|
|
|
32
36
|
mode={this.props.mode}
|
|
33
37
|
testID="dateTimePicker"
|
|
34
38
|
value={this.props.value ? new Date(this.props.value) : new Date()}
|
|
35
|
-
onChange={(
|
|
39
|
+
onChange={(event: any, date?: Date) => {
|
|
36
40
|
if (!date) {
|
|
37
41
|
return;
|
|
38
42
|
}
|
package/src/Tooltip.tsx
CHANGED
|
@@ -34,6 +34,12 @@ interface Measurement {
|
|
|
34
34
|
idealPosition?: TooltipPosition;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
interface ChildrenProps {
|
|
38
|
+
onClick?: () => void | Promise<void>;
|
|
39
|
+
onHoverIn?: () => void;
|
|
40
|
+
onHoverOut?: () => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
37
43
|
const getTooltipPosition = ({
|
|
38
44
|
children,
|
|
39
45
|
tooltip,
|
|
@@ -177,38 +183,25 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
|
|
|
177
183
|
measured: false,
|
|
178
184
|
});
|
|
179
185
|
|
|
180
|
-
const showTooltipTimer = useRef<NodeJS.Timeout>();
|
|
181
|
-
const hideTooltipTimer = useRef<NodeJS.Timeout>();
|
|
186
|
+
const showTooltipTimer = useRef<NodeJS.Timeout | undefined>(undefined);
|
|
187
|
+
const hideTooltipTimer = useRef<NodeJS.Timeout | undefined>(undefined);
|
|
182
188
|
const childrenWrapperRef = useRef<View>(null);
|
|
183
189
|
const touched = useRef(false);
|
|
184
190
|
const isWeb = Platform.OS === "web";
|
|
185
|
-
const resetMeasurement = useCallback(() => {
|
|
186
|
-
setMeasurement({
|
|
187
|
-
children: {},
|
|
188
|
-
tooltip: {},
|
|
189
|
-
measured: false,
|
|
190
|
-
});
|
|
191
|
-
}, []);
|
|
192
|
-
const hideTooltip = useCallback(() => {
|
|
193
|
-
if (showTooltipTimer.current) {
|
|
194
|
-
clearTimeout(showTooltipTimer.current);
|
|
195
|
-
}
|
|
196
|
-
if (hideTooltipTimer.current) {
|
|
197
|
-
clearTimeout(hideTooltipTimer.current);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
touched.current = false;
|
|
201
|
-
setVisible(false);
|
|
202
|
-
resetMeasurement();
|
|
203
|
-
}, [resetMeasurement, setVisible]);
|
|
204
191
|
|
|
205
192
|
// If the tooltip is visible, and the user clicks outside of the tooltip, hide it.
|
|
206
193
|
useEffect(() => {
|
|
207
194
|
return () => {
|
|
195
|
+
if (showTooltipTimer.current) {
|
|
196
|
+
clearTimeout(showTooltipTimer.current);
|
|
197
|
+
}
|
|
198
|
+
if (hideTooltipTimer.current) {
|
|
199
|
+
clearTimeout(hideTooltipTimer.current);
|
|
200
|
+
}
|
|
208
201
|
// Hide tooltip on unmount to prevent it from staying stuck on screen
|
|
209
|
-
|
|
202
|
+
setVisible(false);
|
|
210
203
|
};
|
|
211
|
-
}, [
|
|
204
|
+
}, []);
|
|
212
205
|
|
|
213
206
|
const getArrowContainerStyle = (): ViewStyle => {
|
|
214
207
|
if (!includeArrow) {
|
|
@@ -271,11 +264,6 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
|
|
|
271
264
|
);
|
|
272
265
|
|
|
273
266
|
const handleTouchStart = useCallback(() => {
|
|
274
|
-
if (visible) {
|
|
275
|
-
hideTooltip();
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
267
|
if (hideTooltipTimer.current) {
|
|
280
268
|
clearTimeout(hideTooltipTimer.current);
|
|
281
269
|
}
|
|
@@ -284,7 +272,7 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
|
|
|
284
272
|
touched.current = true;
|
|
285
273
|
setVisible(true);
|
|
286
274
|
}, 100);
|
|
287
|
-
}, [
|
|
275
|
+
}, []);
|
|
288
276
|
|
|
289
277
|
const handleHoverIn = useCallback(() => {
|
|
290
278
|
if (hideTooltipTimer.current) {
|
|
@@ -298,28 +286,25 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
|
|
|
298
286
|
}, [hoverDelay]);
|
|
299
287
|
|
|
300
288
|
const handleHoverOut = useCallback(() => {
|
|
289
|
+
touched.current = false;
|
|
301
290
|
if (showTooltipTimer.current) {
|
|
302
291
|
clearTimeout(showTooltipTimer.current);
|
|
303
292
|
}
|
|
304
|
-
if (hideTooltipTimer.current) {
|
|
305
|
-
clearTimeout(hideTooltipTimer.current);
|
|
306
|
-
}
|
|
307
293
|
|
|
308
294
|
hideTooltipTimer.current = setTimeout(() => {
|
|
309
|
-
|
|
295
|
+
setVisible(false);
|
|
296
|
+
setMeasurement({
|
|
297
|
+
children: {},
|
|
298
|
+
tooltip: {},
|
|
299
|
+
measured: false,
|
|
300
|
+
});
|
|
310
301
|
}, hoverEndDelay);
|
|
311
|
-
}, [
|
|
312
|
-
|
|
313
|
-
const handleClick = useCallback(() => {
|
|
314
|
-
if (visible) {
|
|
315
|
-
hideTooltip();
|
|
316
|
-
}
|
|
317
|
-
}, [hideTooltip, visible]);
|
|
302
|
+
}, [hoverEndDelay]);
|
|
318
303
|
|
|
319
304
|
const mobilePressProps = {
|
|
320
305
|
onPress: useCallback(() => {
|
|
321
306
|
if (!touched.current) {
|
|
322
|
-
children.props.onClick?.();
|
|
307
|
+
(children.props as ChildrenProps).onClick?.();
|
|
323
308
|
}
|
|
324
309
|
}, [children.props]),
|
|
325
310
|
};
|
|
@@ -369,7 +354,7 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
|
|
|
369
354
|
borderRadius: theme.radius.default,
|
|
370
355
|
}}
|
|
371
356
|
testID="tooltip-container"
|
|
372
|
-
onPress={
|
|
357
|
+
onPress={() => setVisible(false)}
|
|
373
358
|
>
|
|
374
359
|
<Text color="inverted" size="sm">
|
|
375
360
|
{text}
|
|
@@ -384,13 +369,12 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
|
|
|
384
369
|
hitSlop={{top: 10, bottom: 10, left: 15, right: 15}}
|
|
385
370
|
onPointerEnter={() => {
|
|
386
371
|
handleHoverIn();
|
|
387
|
-
children.props.onHoverIn?.();
|
|
372
|
+
(children.props as ChildrenProps).onHoverIn?.();
|
|
388
373
|
}}
|
|
389
374
|
onPointerLeave={() => {
|
|
390
375
|
handleHoverOut();
|
|
391
|
-
children.props.onHoverOut?.();
|
|
376
|
+
(children.props as ChildrenProps).onHoverOut?.();
|
|
392
377
|
}}
|
|
393
|
-
onPress={isWeb ? handleClick : undefined}
|
|
394
378
|
onTouchStart={handleTouchStart}
|
|
395
379
|
{...(!isWeb && mobilePressProps)}
|
|
396
380
|
>
|
package/src/Unifier.ts
CHANGED
|
@@ -6,7 +6,7 @@ import * as Clipboard from "expo-clipboard";
|
|
|
6
6
|
import * as Haptics from "expo-haptics";
|
|
7
7
|
import {Dimensions, Keyboard, Linking, Platform, Vibration} from "react-native";
|
|
8
8
|
|
|
9
|
-
import {PermissionKind} from "./Common";
|
|
9
|
+
import {FernsTheme, PermissionKind} from "./Common";
|
|
10
10
|
import {requestPermissions} from "./Permissions";
|
|
11
11
|
|
|
12
12
|
declare global {
|
|
@@ -61,6 +61,8 @@ export function changeColorLuminance(hex: string, luminanceChange: Luminance) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
class UnifierClass {
|
|
64
|
+
private _theme?: Partial<FernsTheme>;
|
|
65
|
+
|
|
64
66
|
private _web = false;
|
|
65
67
|
|
|
66
68
|
private _dev = false;
|
package/src/Utilities.tsx
CHANGED
|
@@ -18,7 +18,8 @@ export function mergeInlineStyles(inlineStyle?: any, newStyle?: any) {
|
|
|
18
18
|
|
|
19
19
|
export function isTestUser(profile?: BaseProfile) {
|
|
20
20
|
return (
|
|
21
|
-
profile
|
|
21
|
+
profile &&
|
|
22
|
+
profile.email &&
|
|
22
23
|
(profile.email.indexOf("nang.io") > -1 || profile.email.indexOf("example.com") > -1)
|
|
23
24
|
);
|
|
24
25
|
}
|
|
@@ -56,7 +57,7 @@ order in which you do so doesn't really matter.
|
|
|
56
57
|
*/
|
|
57
58
|
|
|
58
59
|
interface InlineStyle {
|
|
59
|
-
[key: string]: string | number |
|
|
60
|
+
[key: string]: string | number | void;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
// TODO: This type should be opaque, however the Babel parser doesn't support
|
|
@@ -317,7 +318,7 @@ export function formattedCountyCode(state: string, countyName: string): string {
|
|
|
317
318
|
}
|
|
318
319
|
|
|
319
320
|
export function isAPIError(error: any): error is APIError {
|
|
320
|
-
return error
|
|
321
|
+
return error && error.data?.title;
|
|
321
322
|
}
|
|
322
323
|
|
|
323
324
|
export function printAPIError(error: APIError, details = true): string {
|
|
@@ -7,7 +7,7 @@ import {processAddressComponents} from "./Utilities";
|
|
|
7
7
|
|
|
8
8
|
const loadGooglePlacesScript = (googleMapsApiKey: string, callbackName: any): Promise<void> => {
|
|
9
9
|
return new Promise<void>((resolve, reject): undefined => {
|
|
10
|
-
if (window.google
|
|
10
|
+
if (window.google && window.google.maps && window.google.maps.places) {
|
|
11
11
|
resolve();
|
|
12
12
|
return;
|
|
13
13
|
}
|