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.
Files changed (184) hide show
  1. package/dist/Accordion.js +2 -7
  2. package/dist/Accordion.js.map +1 -1
  3. package/dist/ActionSheet.js +11 -14
  4. package/dist/ActionSheet.js.map +1 -1
  5. package/dist/AddressField.js +1 -1
  6. package/dist/AddressField.js.map +1 -1
  7. package/dist/Badge.js +1 -1
  8. package/dist/Badge.js.map +1 -1
  9. package/dist/Banner.js +1 -1
  10. package/dist/Banner.js.map +1 -1
  11. package/dist/Box.js +3 -3
  12. package/dist/Box.js.map +1 -1
  13. package/dist/Button.js +1 -0
  14. package/dist/Button.js.map +1 -1
  15. package/dist/CheckBox.js.map +1 -1
  16. package/dist/Common.d.ts +9 -13
  17. package/dist/Common.js.map +1 -1
  18. package/dist/DataTable.js +2 -1
  19. package/dist/DataTable.js.map +1 -1
  20. package/dist/DateTimeField.js +22 -22
  21. package/dist/DateTimeField.js.map +1 -1
  22. package/dist/EmailField.js +37 -17
  23. package/dist/EmailField.js.map +1 -1
  24. package/dist/ErrorBoundary.d.ts +1 -1
  25. package/dist/FernsProvider.js +1 -1
  26. package/dist/FernsProvider.js.map +1 -1
  27. package/dist/Heading.js +1 -3
  28. package/dist/Heading.js.map +1 -1
  29. package/dist/Hyperlink.js +1 -1
  30. package/dist/Hyperlink.js.map +1 -1
  31. package/dist/IconButton.js +1 -1
  32. package/dist/IconButton.js.map +1 -1
  33. package/dist/Image.js.map +1 -1
  34. package/dist/MobileAddressAutoComplete.js +1 -1
  35. package/dist/MobileAddressAutoComplete.js.map +1 -1
  36. package/dist/Modal.d.ts +1 -1
  37. package/dist/Modal.js +15 -35
  38. package/dist/Modal.js.map +1 -1
  39. package/dist/ModalSheet.d.ts +1 -1
  40. package/dist/ModalSheet.js +1 -1
  41. package/dist/ModalSheet.js.map +1 -1
  42. package/dist/NumberField.js +4 -10
  43. package/dist/NumberField.js.map +1 -1
  44. package/dist/NumberPickerActionSheet.d.ts +3 -1
  45. package/dist/NumberPickerActionSheet.js +3 -0
  46. package/dist/NumberPickerActionSheet.js.map +1 -1
  47. package/dist/Page.js +1 -1
  48. package/dist/Page.js.map +1 -1
  49. package/dist/Pagination.js +2 -2
  50. package/dist/Pagination.js.map +1 -1
  51. package/dist/Permissions.d.ts +1 -1
  52. package/dist/Permissions.js +2 -2
  53. package/dist/Permissions.js.map +1 -1
  54. package/dist/PickerSelect.js +1 -1
  55. package/dist/PickerSelect.js.map +1 -1
  56. package/dist/SectionDivider.js +1 -1
  57. package/dist/SectionDivider.js.map +1 -1
  58. package/dist/SegmentedControl.js.map +1 -1
  59. package/dist/Signature.native.js +2 -2
  60. package/dist/Signature.native.js.map +1 -1
  61. package/dist/SignatureField.js +2 -2
  62. package/dist/SignatureField.js.map +1 -1
  63. package/dist/Slider.js +3 -3
  64. package/dist/Slider.js.map +1 -1
  65. package/dist/SplitPage.js +7 -7
  66. package/dist/SplitPage.js.map +1 -1
  67. package/dist/SplitPage.native.js +6 -4
  68. package/dist/SplitPage.native.js.map +1 -1
  69. package/dist/TapToEdit.js +3 -3
  70. package/dist/TapToEdit.js.map +1 -1
  71. package/dist/Text.js +1 -1
  72. package/dist/Text.js.map +1 -1
  73. package/dist/TextFieldNumberActionSheet.d.ts +4 -2
  74. package/dist/TextFieldNumberActionSheet.js +4 -1
  75. package/dist/TextFieldNumberActionSheet.js.map +1 -1
  76. package/dist/Tooltip.js +21 -39
  77. package/dist/Tooltip.js.map +1 -1
  78. package/dist/Unifier.d.ts +1 -0
  79. package/dist/Unifier.js.map +1 -1
  80. package/dist/Utilities.d.ts +1 -1
  81. package/dist/Utilities.js +3 -2
  82. package/dist/Utilities.js.map +1 -1
  83. package/dist/WebAddressAutocomplete.js +1 -2
  84. package/dist/WebAddressAutocomplete.js.map +1 -1
  85. package/dist/index.d.ts +10 -10
  86. package/dist/index.js +9 -10
  87. package/dist/index.js.map +1 -1
  88. package/dist/jestSetup.d.ts +0 -0
  89. package/dist/jestSetup.js +21 -0
  90. package/dist/jestSetup.js.map +1 -0
  91. package/dist/setupTests.js +22 -0
  92. package/dist/setupTests.js.map +1 -1
  93. package/dist/table/Table.js +15 -14
  94. package/dist/table/Table.js.map +1 -1
  95. package/dist/table/TableHeaderCell.js +2 -2
  96. package/dist/table/TableHeaderCell.js.map +1 -1
  97. package/dist/test-utils.d.ts +4 -1
  98. package/dist/useStoredState.js +2 -4
  99. package/dist/useStoredState.js.map +1 -1
  100. package/package.json +106 -43
  101. package/src/Accordion.tsx +1 -7
  102. package/src/ActionSheet.tsx +22 -26
  103. package/src/AddressField.tsx +1 -1
  104. package/src/Avatar.test.tsx +0 -2
  105. package/src/Badge.tsx +1 -1
  106. package/src/Banner.tsx +1 -1
  107. package/src/Box.test.tsx +70 -71
  108. package/src/Box.tsx +9 -21
  109. package/src/Button.tsx +1 -0
  110. package/src/CheckBox.tsx +1 -7
  111. package/src/Common.ts +21 -14
  112. package/src/DataTable.tsx +4 -3
  113. package/src/DateTimeField.tsx +22 -22
  114. package/src/EmailField.tsx +42 -22
  115. package/src/FernsProvider.tsx +4 -1
  116. package/src/Heading.tsx +1 -3
  117. package/src/Hyperlink.tsx +2 -2
  118. package/src/IconButton.tsx +2 -2
  119. package/src/Image.tsx +0 -1
  120. package/src/MobileAddressAutoComplete.tsx +1 -1
  121. package/src/Modal.tsx +21 -58
  122. package/src/ModalSheet.tsx +1 -1
  123. package/src/NumberField.tsx +4 -10
  124. package/src/NumberPickerActionSheet.tsx +5 -1
  125. package/src/Page.tsx +1 -1
  126. package/src/Pagination.tsx +11 -2
  127. package/src/Permissions.ts +2 -2
  128. package/src/PickerSelect.tsx +1 -1
  129. package/src/SectionDivider.tsx +1 -1
  130. package/src/SegmentedControl.tsx +1 -3
  131. package/src/Signature.native.tsx +2 -2
  132. package/src/SignatureField.tsx +2 -2
  133. package/src/Slider.tsx +17 -10
  134. package/src/SplitPage.native.tsx +4 -2
  135. package/src/SplitPage.tsx +4 -4
  136. package/src/TapToEdit.tsx +7 -3
  137. package/src/Text.tsx +1 -1
  138. package/src/TextArea.test.tsx +43 -27
  139. package/src/TextField.test.tsx +4 -3
  140. package/src/TextFieldNumberActionSheet.tsx +7 -3
  141. package/src/Tooltip.tsx +29 -45
  142. package/src/Unifier.ts +3 -1
  143. package/src/Utilities.tsx +4 -3
  144. package/src/WebAddressAutocomplete.tsx +1 -1
  145. package/src/index.tsx +10 -11
  146. package/src/jestSetup.ts +16 -0
  147. package/src/setupTests.ts +24 -0
  148. package/src/table/Table.tsx +38 -36
  149. package/src/table/TableHeaderCell.tsx +2 -2
  150. package/src/useStoredState.ts +11 -13
  151. package/dist/Accordion.test.d.ts +0 -1
  152. package/dist/Accordion.test.js +0 -71
  153. package/dist/Accordion.test.js.map +0 -1
  154. package/dist/AddressField.test.d.ts +0 -1
  155. package/dist/AddressField.test.js +0 -65
  156. package/dist/AddressField.test.js.map +0 -1
  157. package/dist/Avatar.test.d.ts +0 -1
  158. package/dist/Avatar.test.js +0 -131
  159. package/dist/Avatar.test.js.map +0 -1
  160. package/dist/Badge.test.d.ts +0 -1
  161. package/dist/Badge.test.js +0 -76
  162. package/dist/Badge.test.js.map +0 -1
  163. package/dist/Box.test.d.ts +0 -1
  164. package/dist/Box.test.js +0 -528
  165. package/dist/Box.test.js.map +0 -1
  166. package/dist/DateTimeField.test.d.ts +0 -1
  167. package/dist/DateTimeField.test.js +0 -258
  168. package/dist/DateTimeField.test.js.map +0 -1
  169. package/dist/DateUtilities.test.d.ts +0 -1
  170. package/dist/DateUtilities.test.js +0 -279
  171. package/dist/DateUtilities.test.js.map +0 -1
  172. package/dist/MarkdownView.d.ts +0 -5
  173. package/dist/MarkdownView.js +0 -44
  174. package/dist/MarkdownView.js.map +0 -1
  175. package/dist/TextArea.test.d.ts +0 -1
  176. package/dist/TextArea.test.js +0 -146
  177. package/dist/TextArea.test.js.map +0 -1
  178. package/dist/TextField.test.d.ts +0 -1
  179. package/dist/TextField.test.js +0 -251
  180. package/dist/TextField.test.js.map +0 -1
  181. package/dist/useStoredState.test.d.ts +0 -1
  182. package/dist/useStoredState.test.js +0 -93
  183. package/dist/useStoredState.test.js.map +0 -1
  184. 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["labels"]
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: "white",
157
+ backgroundColor: 'white',
157
158
  borderRadius: 24,
158
- shadowColor: "#000",
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 && <Box alignItems="center">{centerContent}</Box>}
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
+
@@ -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?.(selectedId)}
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?.(selectedId)}
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?.(selectedId)}
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
- }> = ({title, helperText, onlyShowHelperTextWhileEditing}) => {
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" ? (fieldProps.grow ?? true) : undefined}
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 (_error) {
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, Platform, TextStyle} from "react-native";
11
+ import {Platform, Text as NativeText, TextStyle} from "react-native";
12
12
 
13
13
  import {TextProps} from "./Common";
14
14
  import {Hyperlink} from "./Hyperlink";
@@ -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(<TextArea value="" onChange={mockOnChange} />);
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(<TextArea value="" onChange={mockOnChange} />);
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(<TextArea value="" onChange={mockOnChange} />);
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(<TextArea value="" onChange={mockOnChange} />);
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(<TextArea value="" onChange={mockOnChange} />);
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(<TextArea value={undefined} onChange={mockOnChange} />);
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(<TextArea inputRef={mockInputRef} value="" onChange={mockOnChange} />);
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(<TextArea value="test content" onChange={mockOnChange} />);
213
+ const component = renderWithTheme(
214
+ <TextArea value="test content" onChange={mockOnChange} />
215
+ );
200
216
  expect(component.toJSON()).toMatchSnapshot();
201
217
  });
202
218
 
@@ -1,7 +1,8 @@
1
1
  import {act, userEvent} from "@testing-library/react-native";
2
2
  import React from "react";
3
- import {TextField} from "./TextField";
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 _user = userEvent.setup();
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 _user = userEvent.setup();
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
- type NumberPickerActionSheetState = {};
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={(_event: any, date?: Date) => {
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
- hideTooltip();
202
+ setVisible(false);
210
203
  };
211
- }, [hideTooltip]);
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
- }, [hideTooltip, visible]);
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
- hideTooltip();
295
+ setVisible(false);
296
+ setMeasurement({
297
+ children: {},
298
+ tooltip: {},
299
+ measured: false,
300
+ });
310
301
  }, hoverEndDelay);
311
- }, [hideTooltip, hoverEndDelay]);
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={hideTooltip}
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?.email &&
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 | undefined;
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?.data?.title;
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?.maps?.places) {
10
+ if (window.google && window.google.maps && window.google.maps.places) {
11
11
  resolve();
12
12
  return;
13
13
  }