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