ferns-ui 2.0.0-beta.2 → 2.0.0-beta.5

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 (149) hide show
  1. package/dist/Accordion.js +7 -2
  2. package/dist/Accordion.js.map +1 -1
  3. package/dist/ActionSheet.js +14 -11
  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 +0 -1
  14. package/dist/Button.js.map +1 -1
  15. package/dist/CheckBox.js.map +1 -1
  16. package/dist/Common.d.ts +14 -9
  17. package/dist/Common.js.map +1 -1
  18. package/dist/DataTable.js +1 -2
  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 +17 -37
  23. package/dist/EmailField.js.map +1 -1
  24. package/dist/EmojiSelector.d.ts +114 -0
  25. package/dist/EmojiSelector.js +332 -0
  26. package/dist/EmojiSelector.js.map +1 -0
  27. package/dist/FernsProvider.js +1 -1
  28. package/dist/FernsProvider.js.map +1 -1
  29. package/dist/Heading.js +3 -1
  30. package/dist/Heading.js.map +1 -1
  31. package/dist/Hyperlink.js +1 -1
  32. package/dist/Hyperlink.js.map +1 -1
  33. package/dist/IconButton.js +1 -1
  34. package/dist/IconButton.js.map +1 -1
  35. package/dist/Image.js.map +1 -1
  36. package/dist/MarkdownView.d.ts +5 -0
  37. package/dist/MarkdownView.js +44 -0
  38. package/dist/MarkdownView.js.map +1 -0
  39. package/dist/MobileAddressAutoComplete.js +1 -1
  40. package/dist/MobileAddressAutoComplete.js.map +1 -1
  41. package/dist/Modal.d.ts +1 -1
  42. package/dist/Modal.js +35 -15
  43. package/dist/Modal.js.map +1 -1
  44. package/dist/NumberField.js +10 -4
  45. package/dist/NumberField.js.map +1 -1
  46. package/dist/NumberPickerActionSheet.d.ts +1 -3
  47. package/dist/NumberPickerActionSheet.js +0 -3
  48. package/dist/NumberPickerActionSheet.js.map +1 -1
  49. package/dist/Page.js +1 -1
  50. package/dist/Page.js.map +1 -1
  51. package/dist/Pagination.js +2 -2
  52. package/dist/Pagination.js.map +1 -1
  53. package/dist/Permissions.d.ts +1 -1
  54. package/dist/Permissions.js +2 -2
  55. package/dist/Permissions.js.map +1 -1
  56. package/dist/PickerSelect.js +1 -1
  57. package/dist/PickerSelect.js.map +1 -1
  58. package/dist/SectionDivider.js +1 -1
  59. package/dist/SectionDivider.js.map +1 -1
  60. package/dist/SegmentedControl.js.map +1 -1
  61. package/dist/Signature.native.js +2 -2
  62. package/dist/Signature.native.js.map +1 -1
  63. package/dist/SignatureField.js +2 -2
  64. package/dist/SignatureField.js.map +1 -1
  65. package/dist/Slider.js +3 -3
  66. package/dist/Slider.js.map +1 -1
  67. package/dist/SplitPage.js +7 -7
  68. package/dist/SplitPage.js.map +1 -1
  69. package/dist/SplitPage.native.js +4 -6
  70. package/dist/SplitPage.native.js.map +1 -1
  71. package/dist/TapToEdit.js +3 -3
  72. package/dist/TapToEdit.js.map +1 -1
  73. package/dist/Text.js +1 -1
  74. package/dist/Text.js.map +1 -1
  75. package/dist/TextField.js +9 -2
  76. package/dist/TextField.js.map +1 -1
  77. package/dist/TextFieldNumberActionSheet.d.ts +2 -4
  78. package/dist/TextFieldNumberActionSheet.js +1 -4
  79. package/dist/TextFieldNumberActionSheet.js.map +1 -1
  80. package/dist/Tooltip.js +37 -19
  81. package/dist/Tooltip.js.map +1 -1
  82. package/dist/Unifier.d.ts +0 -1
  83. package/dist/Unifier.js.map +1 -1
  84. package/dist/Utilities.d.ts +1 -1
  85. package/dist/Utilities.js +2 -3
  86. package/dist/Utilities.js.map +1 -1
  87. package/dist/WebAddressAutocomplete.js +2 -1
  88. package/dist/WebAddressAutocomplete.js.map +1 -1
  89. package/dist/index.d.ts +11 -10
  90. package/dist/index.js +11 -9
  91. package/dist/index.js.map +1 -1
  92. package/dist/table/Table.js +14 -15
  93. package/dist/table/Table.js.map +1 -1
  94. package/dist/table/TableHeaderCell.js +2 -2
  95. package/dist/table/TableHeaderCell.js.map +1 -1
  96. package/dist/useStoredState.js +4 -2
  97. package/dist/useStoredState.js.map +1 -1
  98. package/package.json +7 -65
  99. package/src/Accordion.tsx +7 -1
  100. package/src/ActionSheet.tsx +26 -22
  101. package/src/AddressField.tsx +1 -1
  102. package/src/Badge.tsx +1 -1
  103. package/src/Banner.tsx +1 -1
  104. package/src/Box.test.tsx +71 -70
  105. package/src/Box.tsx +21 -9
  106. package/src/Button.tsx +0 -1
  107. package/src/CheckBox.tsx +7 -1
  108. package/src/Common.ts +15 -21
  109. package/src/DataTable.tsx +1 -2
  110. package/src/DateTimeField.tsx +22 -22
  111. package/src/EmailField.tsx +22 -42
  112. package/src/EmojiSelector.test.tsx +61 -0
  113. package/src/EmojiSelector.tsx +510 -0
  114. package/src/FernsProvider.tsx +1 -4
  115. package/src/Heading.tsx +3 -1
  116. package/src/Hyperlink.tsx +1 -1
  117. package/src/IconButton.tsx +2 -2
  118. package/src/Image.tsx +1 -0
  119. package/src/MarkdownView.tsx +67 -0
  120. package/src/MobileAddressAutoComplete.tsx +1 -1
  121. package/src/Modal.tsx +58 -21
  122. package/src/NumberField.tsx +10 -4
  123. package/src/NumberPickerActionSheet.tsx +1 -5
  124. package/src/Page.tsx +1 -1
  125. package/src/Pagination.tsx +2 -11
  126. package/src/Permissions.ts +2 -2
  127. package/src/PickerSelect.tsx +1 -1
  128. package/src/SectionDivider.tsx +1 -1
  129. package/src/SegmentedControl.tsx +3 -1
  130. package/src/Signature.native.tsx +2 -2
  131. package/src/SignatureField.tsx +2 -2
  132. package/src/Slider.tsx +10 -17
  133. package/src/SplitPage.native.tsx +2 -4
  134. package/src/SplitPage.tsx +4 -4
  135. package/src/TapToEdit.tsx +3 -7
  136. package/src/Text.tsx +1 -1
  137. package/src/TextArea.test.tsx +27 -43
  138. package/src/TextField.test.tsx +64 -5
  139. package/src/TextField.tsx +10 -1
  140. package/src/TextFieldNumberActionSheet.tsx +3 -7
  141. package/src/Tooltip.tsx +41 -19
  142. package/src/Unifier.ts +1 -3
  143. package/src/Utilities.tsx +3 -4
  144. package/src/WebAddressAutocomplete.tsx +1 -1
  145. package/src/__snapshots__/EmojiSelector.test.tsx.snap +604 -0
  146. package/src/index.tsx +12 -10
  147. package/src/table/Table.tsx +34 -36
  148. package/src/table/TableHeaderCell.tsx +2 -2
  149. package/src/useStoredState.ts +13 -11
@@ -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
- import {act, userEvent} from "@testing-library/react-native";
1
+ import {act, fireEvent, 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
  );
@@ -115,6 +114,66 @@ describe("TextField", () => {
115
114
 
116
115
  expect(mockOnEnter).toHaveBeenCalledTimes(1);
117
116
  });
117
+
118
+ it("should trim value on blur if trimOnBlur is true, even if onBlur prop is not provided", () => {
119
+ const {getByDisplayValue} = renderWithTheme(
120
+ <TextField value="test " trimOnBlur onChange={mockOnChange} />
121
+ );
122
+
123
+ const input = getByDisplayValue("test ");
124
+
125
+ fireEvent(input, "blur");
126
+
127
+ // on change should be called with trimmed value
128
+ expect(mockOnChange).toHaveBeenCalled();
129
+ const lastCall = mockOnChange.mock.calls.at(-1);
130
+ expect(lastCall?.[0]).toBe("test");
131
+ });
132
+
133
+ it("should trim value on blur if trimOnBlur is true, with onBlur prop provided", async () => {
134
+ const {getByDisplayValue} = renderWithTheme(
135
+ <TextField value="test " trimOnBlur={true} onChange={mockOnChange} onBlur={mockOnBlur} />
136
+ );
137
+
138
+ const input = getByDisplayValue("test ");
139
+
140
+ fireEvent(input, "blur");
141
+
142
+ // onChange should be called with trimmed value
143
+ expect(mockOnChange).toHaveBeenCalled();
144
+ const lastCall = mockOnChange.mock.calls[mockOnChange.mock.calls.length - 1];
145
+ expect(lastCall[0]).toBe("test");
146
+
147
+ // onBlur should also be called with trimmed value
148
+ expect(mockOnBlur).toHaveBeenCalledTimes(1);
149
+ expect(mockOnBlur.mock.calls[0][0]).toBe("test");
150
+ });
151
+
152
+ it("should NOT trim value on blur if trimOnBlur is false", async () => {
153
+ const {getByDisplayValue} = renderWithTheme(
154
+ <TextField value="test " trimOnBlur={false} onChange={mockOnChange} />
155
+ );
156
+
157
+ const input = getByDisplayValue("test ");
158
+ fireEvent(input, "blur");
159
+
160
+ // onChange should not be called because the value hasn't changed (no trimming)
161
+ expect(mockOnChange).not.toHaveBeenCalled();
162
+ });
163
+
164
+ it("trims on blur by default when no prop is provided", async () => {
165
+ const {getByDisplayValue} = renderWithTheme(
166
+ <TextField value="test " onChange={mockOnChange} />
167
+ );
168
+
169
+ const input = getByDisplayValue("test ");
170
+ fireEvent(input, "blur");
171
+
172
+ // onChange should be called with trimmed value
173
+ expect(mockOnChange).toHaveBeenCalled();
174
+ const lastCall = mockOnChange.mock.calls.at(-1);
175
+ expect(lastCall?.[0]).toBe("test");
176
+ });
118
177
  });
119
178
 
120
179
  describe("field types", () => {
@@ -206,7 +265,7 @@ describe("TextField", () => {
206
265
  });
207
266
 
208
267
  it("should not call onFocus when disabled", async () => {
209
- const user = userEvent.setup();
268
+ const _user = userEvent.setup();
210
269
  const {getByDisplayValue} = renderWithTheme(
211
270
  <TextField disabled value="" onChange={mockOnChange} onFocus={mockOnFocus} />
212
271
  );
package/src/TextField.tsx CHANGED
@@ -65,6 +65,7 @@ export const TextField: FC<TextFieldProps> = ({
65
65
  blurOnSubmit = true,
66
66
  iconName,
67
67
  onIconClick,
68
+ trimOnBlur = true,
68
69
  type = "text",
69
70
  autoComplete,
70
71
  inputRef,
@@ -183,8 +184,16 @@ export const TextField: FC<TextFieldProps> = ({
183
184
  value={value}
184
185
  onBlur={() => {
185
186
  if (disabled) return;
187
+ let finalValue = value ?? "";
188
+
189
+ if (trimOnBlur && value) {
190
+ finalValue = finalValue.trim();
191
+ if (finalValue !== value) {
192
+ onChange(finalValue);
193
+ }
194
+ }
186
195
  if (onBlur) {
187
- onBlur(value ?? "");
196
+ onBlur(finalValue);
188
197
  }
189
198
  setFocused(false);
190
199
  }}
@@ -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
@@ -35,7 +35,7 @@ interface Measurement {
35
35
  }
36
36
 
37
37
  interface ChildrenProps {
38
- onClick?: () => void | Promise<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
- setVisible(false);
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
- setVisible(false);
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={() => setVisible(false)}
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 {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
  }