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/DataTable.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import {FontAwesome6} from "@expo/vector-icons";
2
2
  import React, {FC, useCallback, useMemo, useRef, useState} from "react";
3
3
  import {NativeScrollEvent, NativeSyntheticEvent, Pressable, ScrollView, View} from "react-native";
4
+ // @ts-ignore
4
5
  import Markdown from "react-native-markdown-display";
5
6
 
6
7
  import {Box} from "./Box";
@@ -17,9 +18,9 @@ import {Icon} from "./Icon";
17
18
  import {InfoModalIcon} from "./InfoModalIcon";
18
19
  import {Modal} from "./Modal";
19
20
  import {Pagination} from "./Pagination";
21
+ import {TableTitle} from "./table/TableTitle";
20
22
  import {Text} from "./Text";
21
23
  import {useTheme} from "./Theme";
22
- import {TableTitle} from "./table/TableTitle";
23
24
 
24
25
  // TODO: Add permanent horizontal scroll bar so users with only a mouse can scroll left/right
25
26
  // easily.
@@ -298,7 +299,7 @@ interface DataTableHeaderProps {
298
299
  hasMoreContent: boolean;
299
300
  pinnedColumns: number;
300
301
  columnWidths: number[];
301
- headerScrollRef: React.RefObject<ScrollView>;
302
+ headerScrollRef: React.RefObject<ScrollView | null>;
302
303
  sortColumn?: ColumnSortInterface;
303
304
  onSort: (index: number) => void;
304
305
  onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>, isHeader: boolean) => void;
@@ -399,7 +400,7 @@ interface DataTableContentProps {
399
400
  pinnedColumns: number;
400
401
  alternateRowBackground: boolean;
401
402
  columnWidths: number[];
402
- bodyScrollRef: React.RefObject<ScrollView>;
403
+ bodyScrollRef: React.RefObject<ScrollView | null>;
403
404
  onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>, isHeader: boolean) => void;
404
405
  moreContentComponent?: React.ComponentType<{
405
406
  column: DataTableColumn;
@@ -262,20 +262,20 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
262
262
  if (type === "date" || type === "datetime") {
263
263
  if (fieldIndex === 0) {
264
264
  // Month
265
- const monthNum = parseInt(fieldValue, 10);
266
- if (Number.isNaN(monthNum) || monthNum < 1 || monthNum > 12) {
265
+ const monthNum = parseInt(fieldValue);
266
+ if (isNaN(monthNum) || monthNum < 1 || monthNum > 12) {
267
267
  return "Month must be between 1 and 12";
268
268
  }
269
269
  } else if (fieldIndex === 1) {
270
270
  // Day
271
- const dayNum = parseInt(fieldValue, 10);
272
- if (Number.isNaN(dayNum) || dayNum < 1 || dayNum > 31) {
271
+ const dayNum = parseInt(fieldValue);
272
+ if (isNaN(dayNum) || dayNum < 1 || dayNum > 31) {
273
273
  return "Day must be between 1 and 31";
274
274
  }
275
275
  } else if (fieldIndex === 2) {
276
276
  // Year
277
- const yearNum = parseInt(fieldValue, 10);
278
- if (Number.isNaN(yearNum) || yearNum < 1900 || yearNum > 2100) {
277
+ const yearNum = parseInt(fieldValue);
278
+ if (isNaN(yearNum) || yearNum < 1900 || yearNum > 2100) {
279
279
  return "Year must be between 1900 and 2100";
280
280
  }
281
281
  }
@@ -284,14 +284,14 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
284
284
  if (type === "time" || type === "datetime") {
285
285
  if (fieldIndex === (type === "time" ? 0 : 3)) {
286
286
  // Hour
287
- const hourNum = parseInt(fieldValue, 10);
288
- if (Number.isNaN(hourNum) || hourNum < 1 || hourNum > 12) {
287
+ const hourNum = parseInt(fieldValue);
288
+ if (isNaN(hourNum) || hourNum < 1 || hourNum > 12) {
289
289
  return "Hour must be between 1 and 12";
290
290
  }
291
291
  } else if (fieldIndex === (type === "time" ? 1 : 4)) {
292
292
  // Minute
293
- const minuteNum = parseInt(fieldValue, 10);
294
- if (Number.isNaN(minuteNum) || minuteNum < 0 || minuteNum > 59) {
293
+ const minuteNum = parseInt(fieldValue);
294
+ if (isNaN(minuteNum) || minuteNum < 0 || minuteNum > 59) {
295
295
  return "Minute must be between 0 and 59";
296
296
  }
297
297
  }
@@ -323,7 +323,7 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
323
323
  if (!monthVal || !dayVal || !yearVal || !hour || !minuteVal) {
324
324
  return undefined;
325
325
  }
326
- let hourNum = parseInt(hourVal, 10);
326
+ let hourNum = parseInt(hourVal);
327
327
  if (ampPmVal === "pm" && hourNum !== 12) {
328
328
  hourNum += 12;
329
329
  } else if (ampPmVal === "am" && hourNum === 12) {
@@ -331,11 +331,11 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
331
331
  }
332
332
  date = DateTime.fromObject(
333
333
  {
334
- year: parseInt(yearVal, 10),
335
- month: parseInt(monthVal, 10),
336
- day: parseInt(dayVal, 10),
334
+ year: parseInt(yearVal),
335
+ month: parseInt(monthVal),
336
+ day: parseInt(dayVal),
337
337
  hour: hourNum,
338
- minute: parseInt(minuteVal, 10),
338
+ minute: parseInt(minuteVal),
339
339
  second: 0,
340
340
  millisecond: 0,
341
341
  },
@@ -349,9 +349,9 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
349
349
  }
350
350
  date = DateTime.fromObject(
351
351
  {
352
- year: parseInt(yearVal, 10),
353
- month: parseInt(monthVal, 10),
354
- day: parseInt(dayVal, 10),
352
+ year: parseInt(yearVal),
353
+ month: parseInt(monthVal),
354
+ day: parseInt(dayVal),
355
355
  hour: 0,
356
356
  minute: 0,
357
357
  second: 0,
@@ -365,7 +365,7 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
365
365
  if (!hour || !minuteVal) {
366
366
  return undefined;
367
367
  }
368
- let hourNum = parseInt(hour, 10);
368
+ let hourNum = parseInt(hour);
369
369
  if (ampPmVal === "pm" && hourNum !== 12) {
370
370
  hourNum += 12;
371
371
  } else if (ampPmVal === "am" && hourNum === 12) {
@@ -374,7 +374,7 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
374
374
  date = DateTime.fromObject(
375
375
  {
376
376
  hour: hourNum,
377
- minute: parseInt(minuteVal, 10),
377
+ minute: parseInt(minuteVal),
378
378
  second: 0,
379
379
  millisecond: 0,
380
380
  },
@@ -404,7 +404,7 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
404
404
  // so it's always a valid time and easier to edit.
405
405
  // This lets users freely edit or clear the minute field without breaking the time format.
406
406
  const finalValue = numericValue === "" ? "00" : numericValue.slice(-2);
407
- const minuteNum = parseInt(finalValue, 10);
407
+ const minuteNum = parseInt(finalValue);
408
408
 
409
409
  // Update the minute state so the UI reflects the latest input,
410
410
  // even if it's temporarily invalid
@@ -412,7 +412,7 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
412
412
  setMinute(finalValue);
413
413
 
414
414
  // Only update ref and result if it's a valid minute value
415
- if (!Number.isNaN(minuteNum) && minuteNum >= 0 && minuteNum <= 59) {
415
+ if (!isNaN(minuteNum) && minuteNum >= 0 && minuteNum <= 59) {
416
416
  pendingValueRef.current = {minute: finalValue};
417
417
  setFieldErrors((prev) => ({...prev, [index]: undefined}));
418
418
 
@@ -13,11 +13,7 @@ export const EmailField: FC<EmailFieldProps> = ({
13
13
  ...rest
14
14
  }) => {
15
15
  const [localValue, setLocalValue] = useState<string>(value || "");
16
-
17
- // Sync local state with incoming prop values
18
- useEffect(() => {
19
- setLocalValue(value || "");
20
- }, [value]);
16
+ const [error, setError] = useState<string | undefined>(errorText);
21
17
 
22
18
  const validateEmail = useCallback((email: string): string | undefined => {
23
19
  if (email.trim() === "") {
@@ -30,36 +26,60 @@ export const EmailField: FC<EmailFieldProps> = ({
30
26
  return undefined;
31
27
  }, []);
32
28
 
33
- const localOnChange = useCallback(
34
- (e: string) => {
35
- setLocalValue(e);
36
- const err = validateEmail(e);
37
- if (!err && onChange) {
38
- onChange(e);
29
+ // Sync local state with incoming prop values
30
+ useEffect(() => {
31
+ setLocalValue(value || "");
32
+ setError(errorText);
33
+ }, [value, errorText]);
34
+
35
+ const handleBlur = useCallback(
36
+ (email: string) => {
37
+ if (onBlur) {
38
+ onBlur(email);
39
+ }
40
+ const validationError = validateEmail(email);
41
+ if (validationError) {
42
+ setError(validationError);
43
+ } else {
44
+ setError(undefined);
39
45
  }
40
46
  },
41
- [onChange, validateEmail, setLocalValue]
47
+ [onBlur, validateEmail]
42
48
  );
43
49
 
44
- const localOnBlur = useCallback(
45
- (e: string) => {
46
- setLocalValue(e);
47
- const err = validateEmail(e);
48
- if (!err && onBlur) {
49
- onBlur(e);
50
+ const handleChange = useCallback(
51
+ (email: string) => {
52
+ setLocalValue(email);
53
+ const validationError = validateEmail(email);
54
+ if (error && !validationError) {
55
+ setError(undefined);
56
+ }
57
+ if (!validationError) {
58
+ onChange(email);
50
59
  }
51
60
  },
52
- [onBlur, validateEmail]
61
+ [onChange, error, validateEmail]
53
62
  );
63
+
54
64
  return (
55
65
  <TextField
56
- errorText={errorText || validateEmail(localValue)}
66
+ errorText={error}
57
67
  iconName={iconName}
58
68
  placeholder={placeholder}
59
69
  type="email"
60
70
  value={localValue}
61
- onBlur={localOnBlur}
62
- onChange={localOnChange}
71
+ onBlur={(e) => {
72
+ handleBlur(e);
73
+ if (onBlur) {
74
+ onBlur(value || "");
75
+ }
76
+ }}
77
+ onChange={(e) => {
78
+ handleChange(e);
79
+ if (onChange) {
80
+ onChange;
81
+ }
82
+ }}
63
83
  {...rest}
64
84
  />
65
85
  );
@@ -9,7 +9,10 @@ import {Toast} from "./Toast";
9
9
  export const FernsProvider: FC<{
10
10
  children: React.ReactNode;
11
11
  openAPISpecUrl?: string;
12
- }> = ({children, openAPISpecUrl}) => {
12
+ }> = ({
13
+ children,
14
+ openAPISpecUrl,
15
+ }) => {
13
16
  return (
14
17
  <ThemeProvider>
15
18
  <ToastProvider
package/src/Heading.tsx CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  useFonts,
5
5
  } from "@expo-google-fonts/titillium-web";
6
6
  import React from "react";
7
- import {Text as NativeText, Platform, StyleProp, TextStyle} from "react-native";
7
+ import {Platform, StyleProp, Text as NativeText, TextStyle} from "react-native";
8
8
 
9
9
  import {HeadingProps} from "./Common";
10
10
  import {useTheme} from "./Theme";
@@ -14,7 +14,6 @@ const fontSizeAndWeightWeb = {
14
14
  md: {size: 18, weight: "bold"},
15
15
  lg: {size: 24, weight: "bold"},
16
16
  xl: {size: 32, weight: "bold"},
17
- "2xl": {size: 48, weight: "bold"},
18
17
  };
19
18
 
20
19
  const fontSizeAndWeighMobile = {
@@ -22,7 +21,6 @@ const fontSizeAndWeighMobile = {
22
21
  md: {size: 16, weight: "bold"},
23
22
  lg: {size: 20, weight: "bold"},
24
23
  xl: {size: 28, weight: "bold"},
25
- "2xl": {size: 32, weight: "bold"},
26
24
  };
27
25
 
28
26
  const fontSizes = Platform.OS === "web" ? fontSizeAndWeightWeb : fontSizeAndWeighMobile;
package/src/Hyperlink.tsx CHANGED
@@ -111,7 +111,7 @@ class HyperlinkComponent extends React.Component<HyperlinkProps> {
111
111
  component.props.children.substring(_lastIndex, component.props.children.length)
112
112
  );
113
113
  return React.cloneElement(component, componentProps, elements);
114
- } catch (_error) {
114
+ } catch (error) {
115
115
  return component;
116
116
  }
117
117
  };
@@ -156,7 +156,7 @@ class HyperlinkComponent extends React.Component<HyperlinkProps> {
156
156
  <View {...viewProps} style={this.props.style}>
157
157
  {!this.props.onPress && !this.props.onLongPress && !this.props.linkStyle
158
158
  ? this.props.children
159
- : this.parse(this).props.children}
159
+ : (this.parse(this).props as {children?: React.ReactNode}).children}
160
160
  </View>
161
161
  );
162
162
  }
@@ -1,7 +1,7 @@
1
1
  import FontAwesome6 from "@expo/vector-icons/FontAwesome6";
2
2
  import debounce from "lodash/debounce";
3
3
  import React, {FC, useState} from "react";
4
- import {ActivityIndicator, Text as NativeText, Pressable, View} from "react-native";
4
+ import {ActivityIndicator, Pressable, Text as NativeText, View} from "react-native";
5
5
 
6
6
  import {IconButtonProps} from "./Common";
7
7
  import {isMobileDevice} from "./MediaQuery";
@@ -98,7 +98,7 @@ const IconButtonComponent: FC<IconButtonProps> = ({
98
98
  return (
99
99
  <Pressable
100
100
  accessibilityHint={
101
- (accessibilityHint ?? withConfirmation)
101
+ accessibilityHint ?? withConfirmation
102
102
  ? `Opens a confirmation dialog to confirm ${accessLabel}`
103
103
  : `Press to perform ${accessLabel} action`
104
104
  }
package/src/Image.tsx CHANGED
@@ -3,7 +3,6 @@ import {Dimensions, Image as NativeImage} from "react-native";
3
3
 
4
4
  import {Box} from "./Box";
5
5
  import {ImageProps} from "./Common";
6
-
7
6
  const {width: DEVICE_WIDTH} = Dimensions.get("window");
8
7
 
9
8
  export class Image extends React.Component<ImageProps, {}> {
@@ -108,7 +108,7 @@ export const MobileAddressAutocomplete = ({
108
108
  handleAddressChange(event.nativeEvent.text);
109
109
  },
110
110
  }}
111
- onPress={(_data, details = null) => {
111
+ onPress={(data, details = null) => {
112
112
  const addressComponents = details?.address_components;
113
113
  const formattedAddressObject = processAddressComponents(addressComponents, {
114
114
  includeCounty,
package/src/Modal.tsx CHANGED
@@ -1,11 +1,14 @@
1
1
  import React, {FC, useEffect, useRef} from "react";
2
- import {Dimensions, DimensionValue, Pressable, Modal as RNModal, View} from "react-native";
2
+ import {Dimensions, DimensionValue, Modal as RNModal, Pressable, View} from "react-native";
3
3
  import ActionSheet, {ActionSheetRef} from "react-native-actions-sheet";
4
- import {Gesture, GestureDetector} from "react-native-gesture-handler";
5
- import {runOnJS} from "react-native-reanimated";
4
+ import {
5
+ PanGestureHandler,
6
+ PanGestureHandlerStateChangeEvent,
7
+ State,
8
+ } from "react-native-gesture-handler";
6
9
 
7
10
  import {Button} from "./Button";
8
- import type {ModalProps} from "./Common";
11
+ import {ModalProps} from "./Common";
9
12
  import {Heading} from "./Heading";
10
13
  import {Icon} from "./Icon";
11
14
  import {isMobileDevice} from "./MediaQuery";
@@ -126,13 +129,7 @@ const ModalContent: FC<{
126
129
  </View>
127
130
  )}
128
131
  {children && (
129
- <View
130
- style={{
131
- marginTop: text ? 0 : 12,
132
- width: "100%",
133
- flex: isMobile ? undefined : 1,
134
- }}
135
- >
132
+ <View style={{marginTop: text ? 0 : 12, width: "100%", flex: isMobile ? undefined : 1}}>
136
133
  {children}
137
134
  </View>
138
135
  )}
@@ -166,7 +163,6 @@ const ModalContent: FC<{
166
163
 
167
164
  export const Modal: FC<ModalProps> = ({
168
165
  children,
169
- persistOnBackgroundClick = false,
170
166
  primaryButtonDisabled = false,
171
167
  primaryButtonText,
172
168
  secondaryButtonText,
@@ -188,28 +184,23 @@ export const Modal: FC<ModalProps> = ({
188
184
  }
189
185
  };
190
186
 
191
- const handlePrimaryButtonClick = (
192
- value?: Parameters<NonNullable<ModalProps["primaryButtonOnClick"]>>[0]
193
- ) => {
187
+ const handlePrimaryButtonClick = (value?: Parameters<NonNullable<ModalProps["primaryButtonOnClick"]>>[0]) => {
194
188
  if (visible && primaryButtonOnClick) {
195
189
  return primaryButtonOnClick(value);
196
190
  }
197
191
  };
198
192
 
199
- const handleSecondaryButtonClick = (
200
- value?: Parameters<NonNullable<ModalProps["secondaryButtonOnClick"]>>[0]
201
- ) => {
193
+ const handleSecondaryButtonClick = (value?: Parameters<NonNullable<ModalProps["secondaryButtonOnClick"]>>[0]) => {
202
194
  if (visible && secondaryButtonOnClick) {
203
195
  return secondaryButtonOnClick(value);
204
196
  }
205
197
  };
206
198
 
207
- const dragToClose = Gesture.Pan().onEnd((event) => {
208
- if (event.translationY > 20) {
209
- // Gesture callbacks run on the UI thread, runOnJS is required to safely invoke handleDismiss on the JS thread
210
- runOnJS(handleDismiss)();
199
+ const onHandlerStateChange = ({nativeEvent}: PanGestureHandlerStateChangeEvent) => {
200
+ if (nativeEvent.state === State.END && nativeEvent.translationY > 100) {
201
+ handleDismiss();
211
202
  }
212
- });
203
+ };
213
204
 
214
205
  // Open the action sheet ref when the visible prop changes.
215
206
  useEffect(() => {
@@ -222,7 +213,6 @@ export const Modal: FC<ModalProps> = ({
222
213
  const sizePx = getModalSize(size);
223
214
 
224
215
  const modalContentProps = {
225
- persistOnBackgroundClick,
226
216
  title,
227
217
  subtitle,
228
218
  text,
@@ -239,22 +229,13 @@ export const Modal: FC<ModalProps> = ({
239
229
 
240
230
  if (isMobile) {
241
231
  return (
242
- <ActionSheet
243
- ref={actionSheetRef}
244
- closeOnTouchBackdrop={!persistOnBackgroundClick}
245
- // Disable ActionSheet's built-in gestures to avoid conflicts with scrolling
246
- gestureEnabled={false}
247
- onClose={handleDismiss}
248
- >
249
- <View>
250
- {/* Attach our own swipe-to-dismiss gesture to the top handle */}
251
- <GestureDetector gesture={dragToClose}>
232
+ <ActionSheet ref={actionSheetRef} onClose={handleDismiss}>
233
+ <PanGestureHandler onHandlerStateChange={onHandlerStateChange}>
234
+ <View>
252
235
  <View
253
236
  accessibilityHint="Pull down to close the modal"
254
237
  aria-label="Pull down bar"
255
238
  aria-role="adjustable"
256
- // add hitSlop to make the bar easier to hit since it's small
257
- hitSlop={{top: 20, bottom: 20, left: 50, right: 50}}
258
239
  style={{
259
240
  justifyContent: "center",
260
241
  alignItems: "center",
@@ -267,33 +248,15 @@ export const Modal: FC<ModalProps> = ({
267
248
  marginTop: 10,
268
249
  }}
269
250
  />
270
- </GestureDetector>
271
-
272
- <ModalContent {...modalContentProps}>{children}</ModalContent>
273
- </View>
251
+ <ModalContent {...modalContentProps}>{children}</ModalContent>
252
+ </View>
253
+ </PanGestureHandler>
274
254
  </ActionSheet>
275
255
  );
276
256
  } else {
277
257
  return (
278
258
  <RNModal animationType="slide" transparent visible={visible} onRequestClose={handleDismiss}>
279
- <Pressable
280
- style={{
281
- flex: 1,
282
- backgroundColor: "rgba(0, 0, 0, 0.5)",
283
- justifyContent: "center",
284
- alignItems: "center",
285
- }}
286
- onPress={persistOnBackgroundClick ? undefined : handleDismiss}
287
- >
288
- <Pressable
289
- style={{cursor: "auto"}}
290
- onPress={(e) => {
291
- persistOnBackgroundClick ? null : e.stopPropagation();
292
- }}
293
- >
294
- <ModalContent {...modalContentProps}>{children}</ModalContent>
295
- </Pressable>
296
- </Pressable>
259
+ <ModalContent {...modalContentProps}>{children}</ModalContent>
297
260
  </RNModal>
298
261
  );
299
262
  }
@@ -5,7 +5,7 @@ import {Modalize} from "react-native-modalize";
5
5
  import {Portal} from "react-native-portalize";
6
6
 
7
7
  export const useCombinedRefs = (...refs: any) => {
8
- const targetRef = useRef();
8
+ const targetRef = useRef<any>(null);
9
9
 
10
10
  // Iterate through the refs array, and set the ref.current value to the targetRef
11
11
  useEffect(() => {
@@ -24,8 +24,8 @@ export const NumberField: FC<NumberFieldProps> = ({
24
24
  if (!v) {
25
25
  return;
26
26
  }
27
- const num = type === "number" ? parseInt(v, 10) : parseFloat(v);
28
- if (Number.isNaN(num) || (type === "number" && v.match(/[^0-9]/) !== null)) {
27
+ const num = type === "number" ? parseInt(v) : parseFloat(v);
28
+ if (isNaN(num) || (type === "number" && v.match(/[^0-9]/) !== null)) {
29
29
  return "Value must be an integer";
30
30
  } else if (
31
31
  (type === "decimal" && v.match(/[^0-9.]+/) !== null) ||
@@ -47,19 +47,13 @@ export const NumberField: FC<NumberFieldProps> = ({
47
47
  // Only return the value if it is a valid number
48
48
  const localOnChange = useCallback(
49
49
  (v: string) => {
50
- if (type === "decimal" && v === ".") {
51
- // if type is decimal and dot is the first character add 0 before it
52
- setValue("0.");
53
- rest.onChange("0.");
54
- return;
55
- }
50
+ setValue(v);
56
51
  const err = getError(v);
57
52
  if (!err) {
58
- setValue(v);
59
53
  rest.onChange(v);
60
54
  }
61
55
  },
62
- [getError, rest, type]
56
+ [getError, rest]
63
57
  );
64
58
 
65
59
  return <TextField {...rest} errorText={error} value={value} onChange={localOnChange} />;
@@ -9,12 +9,16 @@ import {NumberPickerActionSheetProps} from "./Common";
9
9
 
10
10
  const PICKER_HEIGHT = 104;
11
11
 
12
- type NumberPickerActionSheetState = {};
12
+ interface NumberPickerActionSheetState {}
13
13
 
14
14
  export class NumberPickerActionSheet extends React.Component<
15
15
  NumberPickerActionSheetProps,
16
16
  NumberPickerActionSheetState
17
17
  > {
18
+ constructor(props: NumberPickerActionSheetProps) {
19
+ super(props);
20
+ }
21
+
18
22
  render() {
19
23
  return (
20
24
  <ActionSheet ref={this.props.actionSheetRef} bounceOnOpen gestureEnabled>
package/src/Page.tsx CHANGED
@@ -47,7 +47,7 @@ export class Page extends React.Component<PageProps, {}> {
47
47
  <Button
48
48
  text={this.props.rightButton}
49
49
  variant="muted"
50
- onClick={() => this.props.rightButtonOnClick?.()}
50
+ onClick={() => this.props.rightButtonOnClick && this.props.rightButtonOnClick()}
51
51
  />
52
52
  </Box>
53
53
  )}
@@ -11,7 +11,12 @@ const PaginationButton: FC<{
11
11
  onClick: () => void;
12
12
  totalPages?: number;
13
13
  page?: number;
14
- }> = ({type, onClick, totalPages = 1, page = 1}) => {
14
+ }> = ({
15
+ type,
16
+ onClick,
17
+ totalPages = 1,
18
+ page = 1,
19
+ }) => {
15
20
  let icon: IconName;
16
21
  let disabled = false;
17
22
 
@@ -51,7 +56,11 @@ const PaginationNumber: FC<{
51
56
  number: number | "more";
52
57
  current: boolean;
53
58
  onClick: () => void;
54
- }> = ({number, current, onClick}) => {
59
+ }> = ({
60
+ number,
61
+ current,
62
+ onClick,
63
+ }) => {
55
64
  // Shortcut to make rendering the number buttons easier.
56
65
  if (number === "more") {
57
66
  return <PaginationButton type="more" onClick={() => {}} />;
@@ -4,8 +4,8 @@
4
4
  // import Permissions from "react-native-permissions";
5
5
  import {PermissionKind, PermissionStatus} from "./Common";
6
6
 
7
- export async function requestPermissions(_kind: PermissionKind): Promise<PermissionStatus> {
8
- return new Promise((_resolve, _reject) => {
7
+ export async function requestPermissions(kind: PermissionKind): Promise<PermissionStatus> {
8
+ return new Promise(async (resolve, reject) => {
9
9
  return "denied";
10
10
  });
11
11
  // const userPropertyKey = `PermissionsFor${capitalize(kind)}`;
@@ -201,7 +201,7 @@ export function RNPickerSelect({
201
201
  Keyboard.dismiss();
202
202
  }
203
203
 
204
- setAnimationType(modalProps?.animationType ? modalProps?.animationType : "slide");
204
+ setAnimationType(modalProps && modalProps?.animationType ? modalProps?.animationType : "slide");
205
205
 
206
206
  triggerOpenCloseCallbacks();
207
207
 
@@ -1,6 +1,6 @@
1
+ import {useTheme} from "./Theme";
1
2
  import React from "react";
2
3
  import {View} from "react-native";
3
- import {useTheme} from "./Theme";
4
4
 
5
5
  export const SectionDivider: React.FC<{}> = () => {
6
6
  const {theme} = useTheme();
@@ -24,9 +24,7 @@ export const SegmentedControl: FC<SegmentedControlProps> = ({
24
24
  }, []);
25
25
 
26
26
  const handleNext = useCallback(() => {
27
- setStartIndex((prev) =>
28
- Math.min(items.length - (maxItems ?? items.length), prev + (maxItems ?? 4))
29
- );
27
+ setStartIndex((prev) => Math.min(items.length - (maxItems ?? items.length), prev + (maxItems ?? 4)));
30
28
  }, [items.length, maxItems]);
31
29
 
32
30
  const visibleItems = maxItems ? items.slice(startIndex, startIndex + maxItems) : items;
@@ -21,14 +21,14 @@ export const Signature: FC<Props> = ({onChange, onStart, onEnd}: Props) => {
21
21
  };
22
22
 
23
23
  const onBegin = () => {
24
- onStart?.();
24
+ onStart && onStart();
25
25
  };
26
26
 
27
27
  // Called after end of stroke. Kind of goofy if you ask me,
28
28
  // but you need this in order to trigger the 'onOK' callback that gives us the actual image.
29
29
  const handleEnd = () => {
30
30
  ref.current?.readSignature();
31
- onEnd?.();
31
+ onEnd && onEnd();
32
32
  };
33
33
 
34
34
  return (
@@ -64,10 +64,10 @@ export const SignatureField = ({
64
64
  <Signature
65
65
  onChange={onChange}
66
66
  onEnd={() => {
67
- onEnd?.();
67
+ onEnd && onEnd();
68
68
  }}
69
69
  onStart={() => {
70
- onStart?.();
70
+ onStart && onStart();
71
71
  }}
72
72
  />
73
73
  </View>