ferns-ui 1.12.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/ActionSheet.js +14 -11
  2. package/dist/ActionSheet.js.map +1 -1
  3. package/dist/AddressField.js +1 -1
  4. package/dist/AddressField.js.map +1 -1
  5. package/dist/Badge.js +1 -1
  6. package/dist/Badge.js.map +1 -1
  7. package/dist/Banner.js +1 -1
  8. package/dist/Banner.js.map +1 -1
  9. package/dist/Box.js +3 -3
  10. package/dist/Box.js.map +1 -1
  11. package/dist/Box.test.js +36 -36
  12. package/dist/Box.test.js.map +1 -1
  13. package/dist/Button.js +0 -1
  14. package/dist/Button.js.map +1 -1
  15. package/dist/CheckBox.js.map +1 -1
  16. package/dist/Common.d.ts +6 -6
  17. package/dist/Common.js.map +1 -1
  18. package/dist/DataTable.js +2 -3
  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/FernsProvider.js +1 -1
  23. package/dist/FernsProvider.js.map +1 -1
  24. package/dist/Heading.js +1 -1
  25. package/dist/Heading.js.map +1 -1
  26. package/dist/Hyperlink.js +1 -1
  27. package/dist/Hyperlink.js.map +1 -1
  28. package/dist/IconButton.js +1 -1
  29. package/dist/IconButton.js.map +1 -1
  30. package/dist/Image.js.map +1 -1
  31. package/dist/MobileAddressAutoComplete.js +1 -1
  32. package/dist/MobileAddressAutoComplete.js.map +1 -1
  33. package/dist/Modal.js +13 -3
  34. package/dist/Modal.js.map +1 -1
  35. package/dist/NumberField.js +2 -2
  36. package/dist/NumberField.js.map +1 -1
  37. package/dist/NumberPickerActionSheet.d.ts +1 -3
  38. package/dist/NumberPickerActionSheet.js +0 -3
  39. package/dist/NumberPickerActionSheet.js.map +1 -1
  40. package/dist/Page.js +1 -1
  41. package/dist/Page.js.map +1 -1
  42. package/dist/Pagination.js +2 -2
  43. package/dist/Pagination.js.map +1 -1
  44. package/dist/Permissions.d.ts +1 -1
  45. package/dist/Permissions.js +2 -2
  46. package/dist/Permissions.js.map +1 -1
  47. package/dist/PickerSelect.js +1 -1
  48. package/dist/PickerSelect.js.map +1 -1
  49. package/dist/SectionDivider.js +1 -1
  50. package/dist/SectionDivider.js.map +1 -1
  51. package/dist/SegmentedControl.js.map +1 -1
  52. package/dist/Signature.native.js +2 -2
  53. package/dist/Signature.native.js.map +1 -1
  54. package/dist/SignatureField.js +2 -2
  55. package/dist/SignatureField.js.map +1 -1
  56. package/dist/Slider.js +3 -3
  57. package/dist/Slider.js.map +1 -1
  58. package/dist/SplitPage.js +7 -7
  59. package/dist/SplitPage.js.map +1 -1
  60. package/dist/SplitPage.native.js +4 -6
  61. package/dist/SplitPage.native.js.map +1 -1
  62. package/dist/TapToEdit.js +3 -3
  63. package/dist/TapToEdit.js.map +1 -1
  64. package/dist/Text.js +1 -1
  65. package/dist/Text.js.map +1 -1
  66. package/dist/TextArea.test.js.map +1 -1
  67. package/dist/TextField.test.js +3 -3
  68. package/dist/TextField.test.js.map +1 -1
  69. package/dist/TextFieldNumberActionSheet.d.ts +2 -4
  70. package/dist/TextFieldNumberActionSheet.js +1 -4
  71. package/dist/TextFieldNumberActionSheet.js.map +1 -1
  72. package/dist/Tooltip.js +37 -19
  73. package/dist/Tooltip.js.map +1 -1
  74. package/dist/Unifier.d.ts +0 -1
  75. package/dist/Unifier.js.map +1 -1
  76. package/dist/Utilities.d.ts +1 -1
  77. package/dist/Utilities.js +2 -3
  78. package/dist/Utilities.js.map +1 -1
  79. package/dist/WebAddressAutocomplete.js +2 -1
  80. package/dist/WebAddressAutocomplete.js.map +1 -1
  81. package/dist/index.d.ts +10 -11
  82. package/dist/index.js +10 -10
  83. package/dist/index.js.map +1 -1
  84. package/dist/table/Table.js +14 -15
  85. package/dist/table/Table.js.map +1 -1
  86. package/dist/table/TableHeaderCell.js +2 -2
  87. package/dist/table/TableHeaderCell.js.map +1 -1
  88. package/dist/useStoredState.js +4 -2
  89. package/dist/useStoredState.js.map +1 -1
  90. package/package.json +5 -19
  91. package/src/ActionSheet.tsx +26 -22
  92. package/src/AddressField.tsx +1 -1
  93. package/src/Badge.tsx +1 -1
  94. package/src/Banner.tsx +1 -1
  95. package/src/Box.test.tsx +71 -70
  96. package/src/Box.tsx +21 -9
  97. package/src/Button.tsx +0 -1
  98. package/src/CheckBox.tsx +7 -1
  99. package/src/Common.ts +6 -18
  100. package/src/DataTable.tsx +2 -5
  101. package/src/DateTimeField.tsx +22 -22
  102. package/src/FernsProvider.tsx +1 -4
  103. package/src/Heading.tsx +1 -1
  104. package/src/Hyperlink.tsx +1 -1
  105. package/src/IconButton.tsx +2 -2
  106. package/src/Image.tsx +1 -0
  107. package/src/MobileAddressAutoComplete.tsx +1 -1
  108. package/src/Modal.tsx +27 -4
  109. package/src/NumberField.tsx +2 -2
  110. package/src/NumberPickerActionSheet.tsx +1 -5
  111. package/src/Page.tsx +1 -1
  112. package/src/Pagination.tsx +2 -11
  113. package/src/Permissions.ts +2 -2
  114. package/src/PickerSelect.tsx +1 -1
  115. package/src/SectionDivider.tsx +1 -1
  116. package/src/SegmentedControl.tsx +3 -1
  117. package/src/Signature.native.tsx +2 -2
  118. package/src/SignatureField.tsx +2 -2
  119. package/src/Slider.tsx +10 -17
  120. package/src/SplitPage.native.tsx +2 -4
  121. package/src/SplitPage.tsx +4 -4
  122. package/src/TapToEdit.tsx +3 -7
  123. package/src/Text.tsx +1 -1
  124. package/src/TextArea.test.tsx +27 -43
  125. package/src/TextField.test.tsx +3 -4
  126. package/src/TextFieldNumberActionSheet.tsx +3 -7
  127. package/src/Tooltip.tsx +44 -22
  128. package/src/Unifier.ts +1 -3
  129. package/src/Utilities.tsx +3 -4
  130. package/src/WebAddressAutocomplete.tsx +1 -1
  131. package/src/index.tsx +11 -11
  132. package/src/table/Table.tsx +34 -36
  133. package/src/table/TableHeaderCell.tsx +2 -2
  134. package/src/useStoredState.ts +13 -11
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['labels']
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: 'white',
156
+ backgroundColor: "white",
158
157
  borderRadius: 24,
159
- shadowColor: '#000',
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
-
@@ -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 && 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 && renderContent(selectedId)}
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 && 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 && renderContent(selectedId)}
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 && 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 && renderContent(selectedId)}
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 (error) {
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 {Platform, Text as NativeText, TextStyle} from "react-native";
11
+ import {Text as NativeText, Platform, 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,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
- <TextArea value="" onChange={mockOnChange} />
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
- <TextArea value="" onChange={mockOnChange} />
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
- <TextArea value="" onChange={mockOnChange} />
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
- <TextArea value={undefined} onChange={mockOnChange} />
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
- <TextArea inputRef={mockInputRef} value="" onChange={mockOnChange} />
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
 
@@ -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 user = userEvent.setup();
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 user = userEvent.setup();
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 {NumberPickerActionSheetProps, TextFieldPickerActionSheetProps} from "./Common";
7
+ import {TextFieldPickerActionSheetProps} from "./Common";
8
8
 
9
- interface NumberPickerActionSheetState {}
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={(event: any, date?: Date) => {
35
+ onChange={(_event: any, date?: Date) => {
40
36
  if (!date) {
41
37
  return;
42
38
  }
package/src/Tooltip.tsx CHANGED
@@ -19,20 +19,20 @@ const TOOLTIP_OFFSET = 6;
19
19
  // How many pixels to leave between the tooltip and the edge of the screen
20
20
  const TOOLTIP_OVERFLOW_PADDING = 20;
21
21
 
22
- type ChildrenMeasurement = {
22
+ interface ChildrenMeasurement {
23
23
  width: number;
24
24
  height: number;
25
25
  pageX: number;
26
26
  pageY: number;
27
- };
27
+ }
28
28
 
29
29
  // empty object is a fallback for when the tooltip is not measured yet
30
- type Measurement = {
30
+ interface Measurement {
31
31
  children: ChildrenMeasurement | {};
32
32
  tooltip: LayoutRectangle | {};
33
33
  measured: boolean;
34
34
  idealPosition?: TooltipPosition;
35
- };
35
+ }
36
36
 
37
37
  const getTooltipPosition = ({
38
38
  children,
@@ -182,20 +182,33 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
182
182
  const childrenWrapperRef = useRef<View>(null);
183
183
  const touched = useRef(false);
184
184
  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]);
185
204
 
186
205
  // If the tooltip is visible, and the user clicks outside of the tooltip, hide it.
187
206
  useEffect(() => {
188
207
  return () => {
189
- if (showTooltipTimer.current) {
190
- clearTimeout(showTooltipTimer.current);
191
- }
192
- if (hideTooltipTimer.current) {
193
- clearTimeout(hideTooltipTimer.current);
194
- }
195
208
  // Hide tooltip on unmount to prevent it from staying stuck on screen
196
- setVisible(false);
209
+ hideTooltip();
197
210
  };
198
- }, []);
211
+ }, [hideTooltip]);
199
212
 
200
213
  const getArrowContainerStyle = (): ViewStyle => {
201
214
  if (!includeArrow) {
@@ -258,6 +271,11 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
258
271
  );
259
272
 
260
273
  const handleTouchStart = useCallback(() => {
274
+ if (visible) {
275
+ hideTooltip();
276
+ return;
277
+ }
278
+
261
279
  if (hideTooltipTimer.current) {
262
280
  clearTimeout(hideTooltipTimer.current);
263
281
  }
@@ -266,7 +284,7 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
266
284
  touched.current = true;
267
285
  setVisible(true);
268
286
  }, 100);
269
- }, []);
287
+ }, [hideTooltip, visible]);
270
288
 
271
289
  const handleHoverIn = useCallback(() => {
272
290
  if (hideTooltipTimer.current) {
@@ -280,20 +298,23 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
280
298
  }, [hoverDelay]);
281
299
 
282
300
  const handleHoverOut = useCallback(() => {
283
- touched.current = false;
284
301
  if (showTooltipTimer.current) {
285
302
  clearTimeout(showTooltipTimer.current);
286
303
  }
304
+ if (hideTooltipTimer.current) {
305
+ clearTimeout(hideTooltipTimer.current);
306
+ }
287
307
 
288
308
  hideTooltipTimer.current = setTimeout(() => {
289
- setVisible(false);
290
- setMeasurement({
291
- children: {},
292
- tooltip: {},
293
- measured: false,
294
- });
309
+ hideTooltip();
295
310
  }, hoverEndDelay);
296
- }, [hoverEndDelay]);
311
+ }, [hideTooltip, hoverEndDelay]);
312
+
313
+ const handleClick = useCallback(() => {
314
+ if (visible) {
315
+ hideTooltip();
316
+ }
317
+ }, [hideTooltip, visible]);
297
318
 
298
319
  const mobilePressProps = {
299
320
  onPress: useCallback(() => {
@@ -348,7 +369,7 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
348
369
  borderRadius: theme.radius.default,
349
370
  }}
350
371
  testID="tooltip-container"
351
- onPress={() => setVisible(false)}
372
+ onPress={hideTooltip}
352
373
  >
353
374
  <Text color="inverted" size="sm">
354
375
  {text}
@@ -369,6 +390,7 @@ export const Tooltip: FC<TooltipProps> = ({text, children, idealPosition, includ
369
390
  handleHoverOut();
370
391
  children.props.onHoverOut?.();
371
392
  }}
393
+ onPress={isWeb ? handleClick : undefined}
372
394
  onTouchStart={handleTouchStart}
373
395
  {...(!isWeb && mobilePressProps)}
374
396
  >
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 {FernsTheme, PermissionKind} from "./Common";
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 | void;
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 && error.data?.title;
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 && window.google.maps && window.google.maps.places) {
10
+ if (window.google?.maps?.places) {
11
11
  resolve();
12
12
  return;
13
13
  }