ferns-ui 0.26.0 → 0.26.1

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 (67) hide show
  1. package/dist/ActionSheet.d.ts +1 -1
  2. package/dist/ActionSheet.js +1 -2
  3. package/dist/ActionSheet.js.map +1 -1
  4. package/dist/CheckBox.d.ts +1 -4
  5. package/dist/CheckBox.js +33 -20
  6. package/dist/CheckBox.js.map +1 -1
  7. package/dist/Common.d.ts +44 -10
  8. package/dist/Common.js.map +1 -1
  9. package/dist/DateTimeField.android.d.ts +1 -1
  10. package/dist/DateTimeField.android.js +58 -16
  11. package/dist/DateTimeField.android.js.map +1 -1
  12. package/dist/DateTimeField.d.ts +2 -2
  13. package/dist/DateTimeField.ios.d.ts +1 -1
  14. package/dist/DateTimeField.ios.js +43 -12
  15. package/dist/DateTimeField.ios.js.map +1 -1
  16. package/dist/DateTimeField.js.map +1 -1
  17. package/dist/Field.js +2 -2
  18. package/dist/Field.js.map +1 -1
  19. package/dist/Mask.d.ts +2 -3
  20. package/dist/MediaQuery.d.ts +1 -0
  21. package/dist/MediaQuery.js +16 -0
  22. package/dist/MediaQuery.js.map +1 -1
  23. package/dist/Page.js.map +1 -1
  24. package/dist/SegmentedControl.js +6 -2
  25. package/dist/SegmentedControl.js.map +1 -1
  26. package/dist/SelectList.d.ts +1 -1
  27. package/dist/SelectList.js +9 -8
  28. package/dist/SelectList.js.map +1 -1
  29. package/dist/SplitPage.d.ts +3 -21
  30. package/dist/SplitPage.js +78 -23
  31. package/dist/SplitPage.js.map +1 -1
  32. package/dist/SplitPage.native.d.ts +3 -0
  33. package/dist/SplitPage.native.js +75 -0
  34. package/dist/SplitPage.native.js.map +1 -0
  35. package/dist/TapToEdit.js +11 -17
  36. package/dist/TapToEdit.js.map +1 -1
  37. package/dist/TextField.js +11 -6
  38. package/dist/TextField.js.map +1 -1
  39. package/dist/Utilities.d.ts +1 -0
  40. package/dist/Utilities.js +18 -2
  41. package/dist/Utilities.js.map +1 -1
  42. package/dist/WithLabel.d.ts +3 -3
  43. package/dist/WithLabel.js +3 -0
  44. package/dist/WithLabel.js.map +1 -1
  45. package/dist/index.d.ts +5 -4
  46. package/dist/index.js +1 -0
  47. package/dist/index.js.map +1 -1
  48. package/package.json +2 -1
  49. package/src/ActionSheet.tsx +2 -3
  50. package/src/CheckBox.tsx +85 -60
  51. package/src/Common.ts +49 -10
  52. package/src/DateTimeField.android.tsx +71 -26
  53. package/src/DateTimeField.ios.tsx +55 -13
  54. package/src/DateTimeField.tsx +2 -2
  55. package/src/Field.tsx +4 -4
  56. package/src/Mask.tsx +2 -2
  57. package/src/MediaQuery.ts +14 -0
  58. package/src/Page.tsx +0 -1
  59. package/src/SegmentedControl.tsx +3 -3
  60. package/src/SelectList.tsx +9 -2
  61. package/src/SplitPage.native.tsx +156 -0
  62. package/src/SplitPage.tsx +136 -47
  63. package/src/TapToEdit.tsx +9 -22
  64. package/src/TextField.tsx +26 -17
  65. package/src/Utilities.tsx +24 -3
  66. package/src/WithLabel.tsx +6 -3
  67. package/src/index.tsx +6 -5
package/src/SplitPage.tsx CHANGED
@@ -1,8 +1,9 @@
1
- import React, {Children, ReactChild, ReactElement, useState} from "react";
2
- import {ListRenderItemInfo, ScrollView, View} from "react-native";
1
+ import React, {Children, useCallback, useEffect, useState} from "react";
2
+ import {Dimensions, ListRenderItemInfo, ScrollView, View} from "react-native";
3
+ import {SwiperFlatList} from "react-native-swiper-flatlist";
3
4
 
4
5
  import {Box} from "./Box";
5
- import {Color, SPACING} from "./Common";
6
+ import {SplitPageProps} from "./Common";
6
7
  import {FlatList} from "./FlatList";
7
8
  import {IconButton} from "./IconButton";
8
9
  import {mediaQueryLargerThan} from "./MediaQuery";
@@ -10,24 +11,6 @@ import {SegmentedControl} from "./SegmentedControl";
10
11
  import {Spinner} from "./Spinner";
11
12
  import {Unifier} from "./Unifier";
12
13
 
13
- interface SplitPageProps {
14
- children?: ReactChild | ReactChild[] | null;
15
- tabs?: string[];
16
- // TODO: figure out navigation
17
- navigation?: any;
18
- loading?: boolean;
19
- color?: Color;
20
- keyboardOffset?: number;
21
- renderListViewItem: (itemInfo: ListRenderItemInfo<any>) => ReactElement | null;
22
- renderListViewHeader?: () => ReactElement | null;
23
- renderContent?: (index?: number) => ReactElement | ReactElement[] | null;
24
- listViewData: any[];
25
- listViewExtraData?: any;
26
- listViewWidth?: number;
27
- renderChild?: () => ReactChild;
28
- selectLimit?: number;
29
- }
30
-
31
14
  // A component for rendering a list on one side and a details view on the right for large screens,
32
15
  // and a scrollable list where clicking an item takes you the details view.
33
16
  export const SplitPage = ({
@@ -39,16 +22,41 @@ export const SplitPage = ({
39
22
  renderListViewItem,
40
23
  renderListViewHeader,
41
24
  renderContent,
25
+ onSelectionChange = () => {},
42
26
  listViewData,
43
27
  listViewExtraData,
44
28
  listViewWidth,
29
+ bottomNavBarHeight,
30
+ showItemList,
45
31
  selectLimit,
46
32
  }: SplitPageProps) => {
47
33
  const [selectedId, setSelectedId] = useState<number | undefined>(undefined);
48
34
  const [activeTabs, setActiveTabs] = useState<number[]>(tabs.length > 2 ? [0, 1] : []);
35
+ const {width} = Dimensions.get("window");
36
+
37
+ const isMobileDevice = !mediaQueryLargerThan("sm");
49
38
 
50
39
  const elementArray = Children.toArray(children);
51
40
 
41
+ const onItemSelect = useCallback(
42
+ (item: ListRenderItemInfo<any>) => {
43
+ setSelectedId(item.index);
44
+ onSelectionChange(item);
45
+ },
46
+ [onSelectionChange]
47
+ );
48
+
49
+ const onItemDeselect = useCallback(() => {
50
+ setSelectedId(undefined);
51
+ onSelectionChange(undefined);
52
+ }, [onSelectionChange]);
53
+
54
+ useEffect(() => {
55
+ if (showItemList) {
56
+ onItemDeselect();
57
+ }
58
+ }, [showItemList, onItemDeselect]);
59
+
52
60
  if (!children && !renderContent) {
53
61
  console.warn("A child node is required");
54
62
  return null;
@@ -63,7 +71,7 @@ export const SplitPage = ({
63
71
  return (
64
72
  <Box
65
73
  onClick={() => {
66
- setSelectedId(itemInfo.index);
74
+ onItemSelect(itemInfo);
67
75
  }}
68
76
  >
69
77
  {renderListViewItem(itemInfo)}
@@ -72,19 +80,14 @@ export const SplitPage = ({
72
80
  };
73
81
 
74
82
  const renderList = () => {
75
- if (!mediaQueryLargerThan("sm") && selectedId) {
76
- return null;
77
- }
78
83
  return (
79
84
  <View
80
85
  style={{
81
- width: mediaQueryLargerThan("sm") ? listViewWidth ?? 300 : "100%",
82
- maxWidth: mediaQueryLargerThan("sm") ? listViewWidth ?? 300 : "100%",
86
+ width: listViewWidth ?? 300,
87
+ maxWidth: listViewWidth ?? 300,
83
88
  flexGrow: 1,
84
89
  flexShrink: 0,
85
90
  display: "flex",
86
- paddingTop: "12px",
87
- paddingBottom: "12px",
88
91
  flexDirection: "column",
89
92
  }}
90
93
  >
@@ -102,16 +105,6 @@ export const SplitPage = ({
102
105
  const renderListContent = () => {
103
106
  return (
104
107
  <Box flex="grow" padding={2}>
105
- {!mediaQueryLargerThan("sm") && (
106
- <Box width="100%">
107
- <IconButton
108
- accessibilityLabel="close"
109
- icon="times"
110
- iconColor="darkGray"
111
- onClick={() => setSelectedId(undefined)}
112
- />
113
- </Box>
114
- )}
115
108
  {renderContent && renderContent(selectedId)}
116
109
  </Box>
117
110
  );
@@ -128,7 +121,7 @@ export const SplitPage = ({
128
121
  alignItems: "center",
129
122
  }}
130
123
  >
131
- <Box paddingX={4} paddingY={2} width="100%">
124
+ <Box marginBottom={4} paddingX={4} width="100%">
132
125
  <SegmentedControl
133
126
  items={tabs}
134
127
  multiselect
@@ -143,10 +136,10 @@ export const SplitPage = ({
143
136
  direction="row"
144
137
  flex="grow"
145
138
  height="100%"
146
- paddingX={2}
139
+ paddingX={4}
147
140
  width={activeTabs.length > 1 ? "100%" : "60%"}
148
141
  >
149
- {activeTabs.map((tabIndex) => {
142
+ {activeTabs.map((tabIndex, i) => {
150
143
  return (
151
144
  <ScrollView
152
145
  key={tabIndex}
@@ -156,8 +149,9 @@ export const SplitPage = ({
156
149
  style={{
157
150
  flex: 1,
158
151
  width: "60%",
159
- padding: 3 * SPACING,
160
152
  height: "100%",
153
+ paddingRight: i ? 0 : 16,
154
+ paddingLeft: i ? 16 : 0,
161
155
  }}
162
156
  >
163
157
  {elementArray[tabIndex]}
@@ -180,7 +174,6 @@ export const SplitPage = ({
180
174
  style={{
181
175
  flex: 1,
182
176
  width: "60%",
183
- padding: 3 * SPACING,
184
177
  height: "100%",
185
178
  }}
186
179
  >
@@ -193,21 +186,117 @@ export const SplitPage = ({
193
186
  }
194
187
  };
195
188
 
189
+ const renderMobileList = () => {
190
+ if (isMobileDevice && selectedId !== undefined) {
191
+ return null;
192
+ }
193
+
194
+ return (
195
+ <View
196
+ style={{
197
+ width: "100%",
198
+ maxWidth: "100%",
199
+ height: "100%",
200
+ flexGrow: 1,
201
+ flexShrink: 0,
202
+ display: "flex",
203
+ flexDirection: "column",
204
+ }}
205
+ >
206
+ {renderListViewHeader && renderListViewHeader()}
207
+ <FlatList
208
+ data={listViewData}
209
+ extraData={listViewExtraData}
210
+ keyExtractor={(item) => item.id}
211
+ nestedScrollEnabled
212
+ renderItem={renderItem}
213
+ />
214
+ </View>
215
+ );
216
+ };
217
+
218
+ const renderMobileListContent = () => {
219
+ if (isMobileDevice && selectedId === undefined) {
220
+ return null;
221
+ }
222
+
223
+ return (
224
+ <Box flex="grow" padding={2}>
225
+ {isMobileDevice && (
226
+ <Box width="100%">
227
+ <IconButton
228
+ accessibilityLabel="close"
229
+ icon="times"
230
+ iconColor="darkGray"
231
+ onClick={() => onItemDeselect()}
232
+ />
233
+ </Box>
234
+ )}
235
+ {renderContent && renderContent(selectedId)}
236
+ </Box>
237
+ );
238
+ };
239
+
240
+ const renderMobileChildrenContent = () => {
241
+ if (selectedId === undefined) {
242
+ return null;
243
+ }
244
+ return (
245
+ <SwiperFlatList
246
+ nestedScrollEnabled
247
+ paginationStyle={{justifyContent: "center", width: "95%"}}
248
+ renderAll
249
+ showPagination
250
+ style={{width: "100%"}}
251
+ >
252
+ {elementArray.map((element, i) => {
253
+ return (
254
+ <View
255
+ key={i}
256
+ style={{
257
+ width: width - 8,
258
+ padding: 4,
259
+ height: elementArray.length > 1 ? "90vh" : "100vh",
260
+ paddingBottom: bottomNavBarHeight,
261
+ }}
262
+ >
263
+ {element}
264
+ </View>
265
+ );
266
+ })}
267
+ </SwiperFlatList>
268
+ );
269
+ };
270
+
271
+ const renderSplitPage = () => {
272
+ return (
273
+ <>
274
+ {renderList()}
275
+ {renderContent ? renderListContent() : renderChildrenContent()}
276
+ </>
277
+ );
278
+ };
279
+
280
+ const renderMobileSplitPage = () => {
281
+ const renderMainContent = renderContent
282
+ ? renderMobileListContent()
283
+ : renderMobileChildrenContent();
284
+ return selectedId === undefined ? renderMobileList() : renderMainContent;
285
+ };
286
+
196
287
  return (
197
288
  <Box
198
289
  avoidKeyboard
199
290
  color={color || "lightGray"}
200
291
  direction="row"
201
292
  display="flex"
202
- flex="grow"
203
293
  height="100%"
204
294
  keyboardOffset={keyboardOffset}
205
295
  padding={2}
206
296
  width="100%"
207
297
  >
208
298
  {loading === true && <Spinner color={Unifier.theme.darkGray as any} size="md" />}
209
- {renderList()}
210
- {renderContent ? renderListContent() : renderChildrenContent()}
299
+ {Boolean(isMobileDevice) ? renderMobileSplitPage() : renderSplitPage()}
211
300
  </Box>
212
301
  );
213
302
  };
package/src/TapToEdit.tsx CHANGED
@@ -7,6 +7,7 @@ import {BoxProps} from "./Common";
7
7
  import {Field, FieldProps} from "./Field";
8
8
  import {Icon} from "./Icon";
9
9
  import {Text} from "./Text";
10
+ import {formatAddress} from "./Utilities";
10
11
 
11
12
  export interface TapToEditProps extends Omit<FieldProps, "onChange" | "value"> {
12
13
  title: string;
@@ -113,29 +114,15 @@ export const TapToEdit = ({
113
114
  displayValue = value.join(", ");
114
115
  } else if (fieldProps?.type === "url") {
115
116
  // Show only the domain, full links are likely too long.
116
- const url = new URL(value);
117
- displayValue = url?.hostname ?? value;
118
- } else if (fieldProps?.type === "address") {
119
- let city = "";
120
- if (value?.city) {
121
- city = value?.state || value.zipcode ? `${value.city}, ` : `${value.city}`;
122
- }
123
-
124
- let state = "";
125
- if (value?.state) {
126
- state = value?.zipcode ? `${value.state} ` : `${value.state}`;
117
+ try {
118
+ const url = new URL(value);
119
+ displayValue = url?.hostname ?? value;
120
+ } catch (e) {
121
+ console.debug(`Invalid URL: ${value}`);
122
+ displayValue = value;
127
123
  }
128
-
129
- const zip = value?.zipcode || "";
130
-
131
- const addressLineOne = value?.address1 ?? "";
132
- const addressLineTwo = value?.address2 ?? "";
133
- const addressLineThree = `${city}${state}${zip}`;
134
-
135
- // Only add new lines if lines before and after are not empty to avoid awkward whitespace
136
- displayValue = `${addressLineOne}${
137
- addressLineOne && (addressLineTwo || addressLineThree) ? `\n` : ""
138
- }${addressLineTwo}${addressLineTwo && addressLineThree ? `\n` : ""}${addressLineThree}`;
124
+ } else if (fieldProps?.type === "address") {
125
+ displayValue = formatAddress(value);
139
126
  }
140
127
  }
141
128
 
package/src/TextField.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import {AsYouType} from "libphonenumber-js";
2
2
  import moment from "moment-timezone";
3
- import React, {ReactElement, useState} from "react";
3
+ import React, {ReactElement, useCallback, useMemo, useState} from "react";
4
4
  import {ActivityIndicator, KeyboardTypeOptions, Platform, TextInput, View} from "react-native";
5
5
  import {Calendar} from "react-native-calendars";
6
6
 
@@ -168,7 +168,7 @@ export function TextField({
168
168
  borderColor = Unifier.theme.gray;
169
169
  }
170
170
 
171
- const getHeight = () => {
171
+ const getHeight = useCallback(() => {
172
172
  if (grow) {
173
173
  return Math.max(40, height);
174
174
  } else if (multiline) {
@@ -176,7 +176,29 @@ export function TextField({
176
176
  } else {
177
177
  return 40;
178
178
  }
179
- };
179
+ }, [grow, height, multiline]);
180
+
181
+ const defaultTextInputStyles = useMemo(() => {
182
+ const defaultStyles = {
183
+ flex: 1,
184
+ paddingTop: 10,
185
+ paddingRight: 10,
186
+ paddingBottom: 10,
187
+ paddingLeft: 0,
188
+ height: getHeight(),
189
+ width: "100%",
190
+ color: Unifier.theme.darkGray,
191
+ fontFamily: Unifier.theme.primaryFont,
192
+ ...style,
193
+ };
194
+
195
+ if (Platform.OS === "web") {
196
+ defaultStyles.outline = 0;
197
+ }
198
+
199
+ return defaultStyles;
200
+ }, [getHeight, style]);
201
+
180
202
  const isHandledByModal =
181
203
  type === "date" || type === "numberRange" || type === "decimalRange" || type === "height";
182
204
 
@@ -269,20 +291,7 @@ export function TextField({
269
291
  placeholderTextColor={Unifier.theme.gray}
270
292
  returnKeyType={type === "number" || type === "decimal" ? "done" : returnKeyType}
271
293
  secureTextEntry={type === "password"}
272
- style={{
273
- flex: 1,
274
- paddingTop: 10,
275
- paddingRight: 10,
276
- paddingBottom: 10,
277
- paddingLeft: 0,
278
- height: getHeight(),
279
- width: "100%",
280
- color: Unifier.theme.darkGray,
281
- fontFamily: Unifier.theme.primaryFont,
282
- // Remove border in web.
283
- outlineWidth: 0,
284
- ...style,
285
- }}
294
+ style={defaultTextInputStyles}
286
295
  // For react-native-autofocus
287
296
  textContentType={textContentType}
288
297
  underlineColorAndroid="transparent"
package/src/Utilities.tsx CHANGED
@@ -4,14 +4,12 @@ import get from "lodash/get";
4
4
 
5
5
  export function mergeInlineStyles(inlineStyle?: any, newStyle?: any) {
6
6
  const inline = get(inlineStyle, "__style");
7
- const dangerouslySetInlineStyle = {
7
+ return {
8
8
  __style: {
9
9
  ...inline,
10
10
  ...newStyle,
11
11
  },
12
12
  };
13
-
14
- return dangerouslySetInlineStyle;
15
13
  }
16
14
 
17
15
  /*
@@ -159,3 +157,26 @@ export const union =
159
157
  <T,>(...fns: Functor<T>[]) =>
160
158
  (val: T) =>
161
159
  concat(fns.map((fn) => fn(val)));
160
+
161
+ export function formatAddress(address: any): string {
162
+ let city = "";
163
+ if (address?.city) {
164
+ city = address?.state || address.zipcode ? `${address.city}, ` : `${address.city}`;
165
+ }
166
+
167
+ let state = "";
168
+ if (address?.state) {
169
+ state = address?.zipcode ? `${address.state} ` : `${address.state}`;
170
+ }
171
+
172
+ const zip = address?.zipcode || "";
173
+
174
+ const addressLineOne = address?.address1 ?? "";
175
+ const addressLineTwo = address?.address2 ?? "";
176
+ const addressLineThree = `${city}${state}${zip}`;
177
+
178
+ // Only add new lines if lines before and after are not empty to avoid awkward whitespace
179
+ return `${addressLineOne}${
180
+ addressLineOne && (addressLineTwo || addressLineThree) ? `\n` : ""
181
+ }${addressLineTwo}${addressLineTwo && addressLineThree ? `\n` : ""}${addressLineThree}`;
182
+ }
package/src/WithLabel.tsx CHANGED
@@ -1,11 +1,11 @@
1
1
  import React from "react";
2
2
 
3
3
  import {Box} from "./Box";
4
- import {AllColors, JustifyContent, TextSize} from "./Common";
4
+ import {AllColors, JustifyContent, ReactChildren, TextSize} from "./Common";
5
5
  import {Text} from "./Text";
6
6
 
7
7
  export interface WithLabelProps {
8
- children: React.ReactNode;
8
+ children?: ReactChildren;
9
9
  show?: boolean;
10
10
  label?: string;
11
11
  labelInline?: boolean;
@@ -24,7 +24,10 @@ export function WithLabel({
24
24
  labelColor,
25
25
  show,
26
26
  children,
27
- }: WithLabelProps) {
27
+ }: WithLabelProps): React.ReactElement | null {
28
+ if (!children) {
29
+ return null;
30
+ }
28
31
  return (
29
32
  <Box
30
33
  direction={labelInline ? "row" : "column"}
package/src/index.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./Constants";
2
2
  export * from "./Common";
3
+ export * from "./MediaQuery";
3
4
  export * from "./ActionSheet";
4
5
  export * from "./Avatar";
5
6
  export * from "./Badge";
@@ -69,7 +70,7 @@ interface Insets {
69
70
  right?: number;
70
71
  }
71
72
 
72
- // Lifted from react-native-navigatino
73
+ // Lifted from react-native-navigation
73
74
  // import {Options} from "./Options";
74
75
  export interface LayoutComponent<P = {}> {
75
76
  /**
@@ -453,7 +454,7 @@ export interface OptionsTopBarTitle {
453
454
  passProps?: object;
454
455
  };
455
456
  /**
456
- * Top Bar title height in densitiy pixels
457
+ * Top Bar title height in density pixels
457
458
  * #### (Android specific)
458
459
  */
459
460
  height?: number;
@@ -496,7 +497,7 @@ export interface OptionsTopBarBackButton {
496
497
  */
497
498
  icon?: ImageRequireSource;
498
499
  /**
499
- * Weither the back button is visible or not
500
+ * Whether the back button is visible or not
500
501
  * @default true
501
502
  */
502
503
  visible?: boolean;
@@ -1005,14 +1006,14 @@ export interface OverlayOptions {
1005
1006
  */
1006
1007
  interceptTouchOutside?: boolean;
1007
1008
  /**
1008
- * Control wether this Overlay should handle Keyboard events.
1009
+ * Control whether this Overlay should handle Keyboard events.
1009
1010
  * Set this to true if your Overlay contains a TextInput.
1010
1011
  */
1011
1012
  handleKeyboardEvents?: boolean;
1012
1013
  }
1013
1014
  export interface ModalOptions {
1014
1015
  /**
1015
- * Control wether this modal should be dismiss using swipe gesture when the modalPresentationStyle = 'pageSheet'
1016
+ * Control whether this modal should be dismiss using swipe gesture when the modalPresentationStyle = 'pageSheet'
1016
1017
  * #### (iOS specific)
1017
1018
  */
1018
1019
  swipeToDismiss?: boolean;