ferns-ui 0.36.4 → 0.36.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 (77) hide show
  1. package/package.json +3 -4
  2. package/src/ActionSheet.tsx +1231 -0
  3. package/src/Avatar.tsx +317 -0
  4. package/src/Badge.tsx +65 -0
  5. package/src/Banner.tsx +124 -0
  6. package/src/BlurBox.native.tsx +40 -0
  7. package/src/BlurBox.tsx +31 -0
  8. package/src/Body.tsx +32 -0
  9. package/src/Box.tsx +308 -0
  10. package/src/Button.tsx +219 -0
  11. package/src/Card.tsx +23 -0
  12. package/src/CheckBox.tsx +118 -0
  13. package/src/Common.ts +2743 -0
  14. package/src/Constants.ts +53 -0
  15. package/src/CustomSelect.tsx +85 -0
  16. package/src/DateTimeActionSheet.tsx +409 -0
  17. package/src/DateTimeField.android.tsx +101 -0
  18. package/src/DateTimeField.ios.tsx +83 -0
  19. package/src/DateTimeField.tsx +69 -0
  20. package/src/DecimalRangeActionSheet.tsx +113 -0
  21. package/src/ErrorBoundary.tsx +37 -0
  22. package/src/ErrorPage.tsx +44 -0
  23. package/src/FernsProvider.tsx +21 -0
  24. package/src/Field.tsx +299 -0
  25. package/src/FieldWithLabels.tsx +36 -0
  26. package/src/FlatList.tsx +2 -0
  27. package/src/Form.tsx +182 -0
  28. package/src/HeaderButtons.tsx +107 -0
  29. package/src/Heading.tsx +53 -0
  30. package/src/HeightActionSheet.tsx +104 -0
  31. package/src/Hyperlink.tsx +181 -0
  32. package/src/Icon.tsx +24 -0
  33. package/src/IconButton.tsx +165 -0
  34. package/src/Image.tsx +50 -0
  35. package/src/ImageBackground.tsx +14 -0
  36. package/src/InfoTooltipButton.tsx +23 -0
  37. package/src/Layer.tsx +17 -0
  38. package/src/Link.tsx +17 -0
  39. package/src/Mask.tsx +21 -0
  40. package/src/MediaQuery.ts +46 -0
  41. package/src/Meta.tsx +9 -0
  42. package/src/Modal.tsx +248 -0
  43. package/src/ModalSheet.tsx +58 -0
  44. package/src/NumberPickerActionSheet.tsx +66 -0
  45. package/src/Page.tsx +133 -0
  46. package/src/Permissions.ts +44 -0
  47. package/src/PickerSelect.tsx +553 -0
  48. package/src/Pill.tsx +24 -0
  49. package/src/Pog.tsx +87 -0
  50. package/src/ProgressBar.tsx +55 -0
  51. package/src/ScrollView.tsx +2 -0
  52. package/src/SegmentedControl.tsx +102 -0
  53. package/src/SelectList.tsx +89 -0
  54. package/src/SideDrawer.tsx +62 -0
  55. package/src/Spinner.tsx +20 -0
  56. package/src/SplitPage.native.tsx +160 -0
  57. package/src/SplitPage.tsx +302 -0
  58. package/src/Switch.tsx +19 -0
  59. package/src/Table.tsx +87 -0
  60. package/src/TableHeader.tsx +36 -0
  61. package/src/TableHeaderCell.tsx +76 -0
  62. package/src/TableRow.tsx +87 -0
  63. package/src/TapToEdit.tsx +221 -0
  64. package/src/Text.tsx +131 -0
  65. package/src/TextArea.tsx +16 -0
  66. package/src/TextField.tsx +401 -0
  67. package/src/TextFieldNumberActionSheet.tsx +61 -0
  68. package/src/Toast.tsx +106 -0
  69. package/src/Tooltip.tsx +269 -0
  70. package/src/UnifiedScreens.ts +24 -0
  71. package/src/Unifier.ts +371 -0
  72. package/src/Utilities.tsx +159 -0
  73. package/src/WithLabel.tsx +57 -0
  74. package/src/dayjsExtended.ts +10 -0
  75. package/src/index.tsx +1346 -0
  76. package/src/polyfill.d.ts +11 -0
  77. package/src/tableContext.tsx +80 -0
package/src/Avatar.tsx ADDED
@@ -0,0 +1,317 @@
1
+ /* eslint-disable no-console */
2
+ import {ImageResult, manipulateAsync, SaveFormat} from "expo-image-manipulator";
3
+ import {launchImageLibraryAsync, MediaTypeOptions} from "expo-image-picker";
4
+ import React, {useEffect, useState} from "react";
5
+ import {Image, ImageResizeMode, Platform, Text, View} from "react-native";
6
+
7
+ import {Box} from "./Box";
8
+ import {AllColors, IconName, UnsignedUpTo12} from "./Common";
9
+ import {Icon} from "./Icon";
10
+ import {isMobileDevice} from "./MediaQuery";
11
+ import {Tooltip} from "./Tooltip";
12
+ import {Unifier} from "./Unifier";
13
+
14
+ const sizes = {
15
+ xs: 24,
16
+ sm: 32,
17
+ md: 48,
18
+ lg: 64,
19
+ xl: 120,
20
+ };
21
+
22
+ const sizeIconPadding: {[id: string]: UnsignedUpTo12} = {
23
+ xs: 0,
24
+ sm: 0,
25
+ md: 1,
26
+ lg: 1,
27
+ xl: 2,
28
+ };
29
+
30
+ const statusIcons: {[id: string]: {icon: IconName; color: AllColors; label: string}} = {
31
+ online: {icon: "circle", color: "green", label: "Online"},
32
+ offline: {icon: "circle", color: "gray", label: "Offline"},
33
+ doNotDisturb: {icon: "minus-circle", color: "red", label: "Do Not Disturb"},
34
+ away: {icon: "moon", color: "orange", label: "Away"},
35
+ meeting: {icon: "calendar", color: "orange", label: "In a Meeting"},
36
+ vacation: {icon: "plane", color: "orange", label: "On Vacation"},
37
+ sick: {icon: "clinic-medical", color: "orange", label: "Sick"},
38
+ outOfOffice: {icon: "clock", color: "orange", label: "Out of Office"},
39
+ commuting: {icon: "car", color: "orange", label: "Commuting"},
40
+ };
41
+
42
+ export type AvatarStatus =
43
+ | "online"
44
+ | "offline"
45
+ | "doNotDisturb"
46
+ | "away"
47
+ | "meeting"
48
+ | "vacation"
49
+ | "sick"
50
+ | "outOfOffice"
51
+ | "commuting";
52
+
53
+ interface AvatarProps {
54
+ // Color for the background of the circle when no src picture is present.
55
+ backgroundColor?: AllColors;
56
+ // Color for the initials when no src picture is present.
57
+ textColor?: AllColors;
58
+ /**
59
+ * The name of the user. This is used for the placeholder treatment if an image is not available.
60
+ */
61
+ name: string;
62
+ /**
63
+ * Override the generated initials from `name`.
64
+ */
65
+ initials?: string;
66
+ /**
67
+ * Adds a white border around Avatar so it's visible when displayed on other images.
68
+ */
69
+ outline?: boolean;
70
+ /**
71
+ * xs: 24px, sm: 32px, md: 48px, lg: 64px, xl: 120px.
72
+ */
73
+ size?: "xs" | "sm" | "md" | "lg" | "xl";
74
+ /**
75
+ * The URL of the user's image.
76
+ */
77
+ src?: string;
78
+ /**
79
+ * The fit for the image within the Avatar: "cover" | "contain" | "none".
80
+ * Default is undefined. See Image.tsx for more info
81
+ */
82
+ imageFit?: "cover" | "contain" | "none";
83
+ /**
84
+ * Allow user to edit the image of the avatar
85
+ */
86
+ editAvatarImage?: boolean;
87
+ /**
88
+ * Function to handle the avatar image edit
89
+ */
90
+ onChange?: (val: any) => void;
91
+ /**
92
+ * Resize image width. If only the width is provided, the image will preserve aspect ratio
93
+ */
94
+ avatarImageWidth?: number;
95
+ /**
96
+ * Resize image height. If avatarImageWidth is also provided, the image aspect ratio may be distorted.
97
+ */
98
+ avatarImageHeight?: number;
99
+ /**
100
+ * The image format that the image will be saved as after any edits by the expo-image-manipulator
101
+ */
102
+ avatarImageFormat?: SaveFormat;
103
+ /**
104
+ * The status of the user to display with the avatar.
105
+ */
106
+ status?: AvatarStatus;
107
+ /**
108
+ * If true, the status indicator will show a mobile icon instead of a dot, if status is one of
109
+ * "online", "away", "offline", or "doNotDisturb". Will show the normal status icon in other cases.
110
+ */
111
+ statusMobile?: boolean;
112
+ /**
113
+ * Text to show when hovering over the avatar image. Only works on web.
114
+ */
115
+ statusText?: string;
116
+ }
117
+
118
+ export const Avatar = (props: AvatarProps): React.ReactElement => {
119
+ const [isImageLoaded, setIsImageLoaded] = useState(true);
120
+ const [hovered, setHovered] = useState(false);
121
+ const [src, setSrc] = useState(props.src ?? undefined);
122
+ const {
123
+ name,
124
+ initials,
125
+ outline,
126
+ size = "md",
127
+ imageFit = "contain",
128
+ editAvatarImage,
129
+ onChange,
130
+ avatarImageWidth = sizes[size],
131
+ avatarImageHeight,
132
+ avatarImageFormat = SaveFormat.PNG,
133
+ } = props;
134
+ const width = sizes[size];
135
+ const height = sizes[size];
136
+ const radius = sizes[size] / 2;
137
+ const fontSize = sizes[size] / 2;
138
+ const computedInitials =
139
+ initials ??
140
+ (name.match(/(^\S\S?|\s\S)?/g) as any)
141
+ .map((v: string) => v.trim())
142
+ .join("")
143
+ .match(/(^\S|\S$)?/g)
144
+ .join("")
145
+ .toLocaleUpperCase();
146
+
147
+ useEffect(() => {
148
+ setSrc(props.src);
149
+ }, [props]);
150
+
151
+ if (editAvatarImage && !onChange) {
152
+ console.warn("Avatars with the editAvatarImage flag on should also have an onChange property.");
153
+ }
154
+
155
+ const handleImageError = () => {
156
+ setIsImageLoaded(false);
157
+ console.warn("Image load error");
158
+ };
159
+
160
+ const pickImage = async () => {
161
+ // TODO: Add permission request to use camera to take a picture
162
+ const result = await launchImageLibraryAsync({
163
+ mediaTypes: MediaTypeOptions.Images,
164
+ allowsEditing: true,
165
+ base64: true,
166
+ });
167
+
168
+ if (!result.canceled && result.assets) {
169
+ const resizedImage = await resizeImage(result.assets[0].uri);
170
+ setSrc(resizedImage.uri);
171
+ if (onChange) {
172
+ onChange({avatarImageFormat, ...resizedImage});
173
+ }
174
+ }
175
+ };
176
+
177
+ const resizeImage = async (imageUri: string): Promise<ImageResult> => {
178
+ return manipulateAsync(
179
+ imageUri,
180
+ [{resize: {width: avatarImageWidth, height: avatarImageHeight}}],
181
+ {format: avatarImageFormat}
182
+ );
183
+ };
184
+
185
+ const renderEditIcon = () => {
186
+ if (editAvatarImage && hovered && Platform.OS === "web") {
187
+ return (
188
+ <Box
189
+ alignItems="center"
190
+ dangerouslySetInlineStyle={{
191
+ __style: {backgroundColor: "rgba(255,255,255,0.5)", borderRadius: radius},
192
+ }}
193
+ height={height}
194
+ justifyContent="center"
195
+ position="absolute"
196
+ // width={width}
197
+ zIndex={5}
198
+ onClick={pickImage}
199
+ >
200
+ <Icon color="darkGray" name="edit" size={size} />
201
+ </Box>
202
+ );
203
+ } else if (editAvatarImage && Platform.OS !== "web") {
204
+ return (
205
+ <Box
206
+ bottom
207
+ left={Boolean(props.status)}
208
+ paddingX={sizeIconPadding[size]}
209
+ position="absolute"
210
+ right={!Boolean(props.status)}
211
+ zIndex={5}
212
+ onClick={pickImage}
213
+ >
214
+ <Icon color="darkGray" name="edit" size={size} />
215
+ </Box>
216
+ );
217
+ }
218
+ return null;
219
+ };
220
+
221
+ const renderStatusIcon = () => {
222
+ if (!props.status) {
223
+ return null;
224
+ }
225
+ // eslint-disable-next-line prefer-const
226
+ let {icon, color} = statusIcons[props.status];
227
+ if (
228
+ props.statusMobile &&
229
+ ["online", "away", "offline", "doNotDisturb"].includes(props.status)
230
+ ) {
231
+ icon = "mobile-alt";
232
+ }
233
+ if (!icon || !color) {
234
+ console.warn(`Avatar: Invalid status ${props.status}`);
235
+ return null;
236
+ }
237
+ return (
238
+ <Box bottom paddingX={sizeIconPadding[size]} position="absolute" right zIndex={5}>
239
+ <Icon color={color} name={icon} size={size} />
240
+ </Box>
241
+ );
242
+ };
243
+
244
+ const avatar = (
245
+ <Box height={height} position="relative" width={width}>
246
+ <Box
247
+ border={outline ? "white" : undefined}
248
+ height={height}
249
+ overflow="hidden"
250
+ position="relative"
251
+ rounding="circle"
252
+ width={width}
253
+ onHoverEnd={() => setHovered(false)}
254
+ onHoverStart={() => setHovered(true)}
255
+ >
256
+ {src && isImageLoaded ? (
257
+ // TODO: Make our Image component rounding work so that we can use it for Avatar. Currently it creates an
258
+ // unrounded box around the Image.
259
+ <Image
260
+ resizeMode={imageFit as ImageResizeMode}
261
+ source={{uri: src, cache: "force-cache"}}
262
+ style={{
263
+ borderRadius: radius,
264
+ height,
265
+ width,
266
+ display: "flex",
267
+ alignItems: "center",
268
+ justifyContent: "center",
269
+ overflow: "hidden",
270
+ }}
271
+ onError={handleImageError}
272
+ />
273
+ ) : (
274
+ <View
275
+ style={{
276
+ height,
277
+ width,
278
+ borderRadius: radius,
279
+ display: "flex",
280
+ alignItems: "center",
281
+ justifyContent: "center",
282
+ backgroundColor: props.backgroundColor
283
+ ? Unifier.theme[props.backgroundColor]
284
+ : Unifier.theme.gray,
285
+ }}
286
+ >
287
+ <Text style={{fontSize, color: props.textColor ?? Unifier.theme.darkGray}}>
288
+ {computedInitials}
289
+ </Text>
290
+ </View>
291
+ )}
292
+ </Box>
293
+ {/* Needs to come after the image so it renders on top. */}
294
+ {renderEditIcon()}
295
+ {renderStatusIcon()}
296
+ </Box>
297
+ );
298
+
299
+ let status = props.statusText;
300
+ if (!status && props.status) {
301
+ status = statusIcons[props.status]?.label;
302
+ }
303
+
304
+ if (status) {
305
+ // Need to wrap the tooltip so it doesn't expand to 100% width and render the tooltip off. Don't show the
306
+ // tooltips on mobile because they intercept the edit avatar clicks.
307
+ return (
308
+ <Box width={width}>
309
+ <Tooltip idealDirection="top" text={isMobileDevice() ? undefined : status}>
310
+ {avatar}
311
+ </Tooltip>
312
+ </Box>
313
+ );
314
+ } else {
315
+ return avatar;
316
+ }
317
+ };
package/src/Badge.tsx ADDED
@@ -0,0 +1,65 @@
1
+ import React from "react";
2
+ import {Text, View} from "react-native";
3
+
4
+ import {AllColors} from "./Common";
5
+ import {Unifier} from "./Unifier";
6
+
7
+ interface BadgeProps {
8
+ // The text to display inside the badge.
9
+ title: string;
10
+ // Position relative to the text. Top should only be used with headings.
11
+ position?: "top" | "middle"; // default "middle"
12
+ // Some default badge types. Occasionally, a custom badge might be required for different color schemes.
13
+ type?: "info" | "error" | "warning" | "success" | "neutral" | "custom"; // default "info
14
+ // If `type` is set to "custom", a custom theme color should be provided.
15
+ color?: AllColors;
16
+ }
17
+
18
+ const BADGE_COLORS: {[key: string]: AllColors} = {
19
+ info: "blue",
20
+ error: "red",
21
+ warning: "orange",
22
+ success: "springGreen",
23
+ neutral: "gray",
24
+ };
25
+
26
+ export function Badge({
27
+ title,
28
+ position = "middle",
29
+ type = "info",
30
+ color,
31
+ }: BadgeProps): React.ReactElement {
32
+ if (color && type !== "custom") {
33
+ console.warn('Badge color only supported when `type` is set to "custom".');
34
+ }
35
+ const badgeColor = type === "custom" ? color! : BADGE_COLORS[type];
36
+
37
+ return (
38
+ <View
39
+ style={{
40
+ backgroundColor: Unifier.theme[badgeColor],
41
+ borderRadius: 2,
42
+ height: 14,
43
+ paddingTop: 2,
44
+ paddingBottom: 2,
45
+ paddingLeft: 4,
46
+ paddingRight: 4,
47
+ marginTop: -4,
48
+ marginLeft: 4,
49
+ display: "flex",
50
+ alignSelf: position === "middle" ? "center" : "flex-start",
51
+ }}
52
+ >
53
+ <Text
54
+ style={{
55
+ fontSize: 10,
56
+ flexWrap: "nowrap",
57
+ color: "white",
58
+ fontWeight: "bold",
59
+ }}
60
+ >
61
+ {title}
62
+ </Text>
63
+ </View>
64
+ );
65
+ }
package/src/Banner.tsx ADDED
@@ -0,0 +1,124 @@
1
+ import React from "react";
2
+
3
+ import {Box} from "./Box";
4
+ import {BoxColor, ButtonColor, Rounding, TextColor} from "./Common";
5
+ import {IconButton} from "./IconButton";
6
+ import {Text} from "./Text";
7
+ import {Unifier} from "./Unifier";
8
+
9
+ // import {faTimesCircle, faArrowRight} from "@fortawesome/free-solid-svg-icons";
10
+
11
+ export interface BannerProps {
12
+ id: string;
13
+ text: string;
14
+ subtext?: string;
15
+ color?: BoxColor;
16
+ textColor?: TextColor;
17
+ negativeXMargin?: number;
18
+ bold?: boolean;
19
+ shape?: Rounding;
20
+ type?: "dismiss" | "action";
21
+ onClick?: () => void;
22
+ }
23
+
24
+ // library.add(faTimesCircle);
25
+ // library.add(faArrowRight);
26
+
27
+ interface BannerState {
28
+ show: boolean;
29
+ }
30
+
31
+ function getKey(id: string) {
32
+ return `@ReactUnifier:${id}`;
33
+ }
34
+
35
+ export const hideBanner = (id: string) => {
36
+ console.debug(`[banner] Hiding ${getKey(id)} `);
37
+ Unifier.storage.setItem(getKey(id), "true");
38
+ };
39
+
40
+ export class Banner extends React.Component<BannerProps, BannerState> {
41
+ state = {show: false};
42
+
43
+ async componentDidMount() {
44
+ const seen = await Unifier.storage.getItem(getKey(this.props.id));
45
+ console.debug(`[banner] ${getKey(this.props.id)} seen? ${seen}`);
46
+ this.setState({show: !seen});
47
+ }
48
+
49
+ dismiss = () => {
50
+ hideBanner(this.props.id);
51
+ this.setState({show: false});
52
+ };
53
+
54
+ render() {
55
+ if (!this.state.show) {
56
+ return null;
57
+ }
58
+ const type = this.props.type || "dismiss";
59
+
60
+ if (type === "action" && !this.props.onClick) {
61
+ console.warn("Banners with type action require an onClick property.");
62
+ }
63
+ const negativeMargin = (this.props.negativeXMargin || 0) * -4;
64
+
65
+ return (
66
+ <Box
67
+ color={this.props.color || "secondaryDark"}
68
+ dangerouslySetInlineStyle={{
69
+ __style: {
70
+ marginLeft: negativeMargin,
71
+ marginRight: negativeMargin,
72
+ },
73
+ }}
74
+ direction="row"
75
+ justifyContent="between"
76
+ marginBottom={3}
77
+ marginTop={3}
78
+ paddingX={3}
79
+ paddingY={2}
80
+ rounding={this.props.shape}
81
+ shadow
82
+ width={Unifier.utils.dimensions().width || "100%"}
83
+ onClick={this.dismiss}
84
+ >
85
+ <Box alignItems="center" direction="column" flex="shrink" justifyContent="center">
86
+ <Box paddingY={1}>
87
+ <Text align="center" color={this.props.textColor || "white"} weight="bold">
88
+ {this.props.text}
89
+ </Text>
90
+ </Box>
91
+ {this.props.subtext && (
92
+ <Box paddingY={1}>
93
+ <Text align="center" color={this.props.textColor || "white"}>
94
+ {this.props.subtext}
95
+ </Text>
96
+ </Box>
97
+ )}
98
+ </Box>
99
+ <Box alignItems="center" display="block" justifyContent="center" width={40}>
100
+ {type === "dismiss" && (
101
+ <IconButton
102
+ accessibilityLabel=""
103
+ icon="times-circle"
104
+ // size="lg"
105
+ iconColor={(this.props.textColor || "white") as ButtonColor}
106
+ prefix="fas"
107
+ onClick={() => this.dismiss()}
108
+ />
109
+ )}
110
+ {type === "action" && (
111
+ <IconButton
112
+ accessibilityLabel=""
113
+ icon="arrow-right"
114
+ // size="lg"
115
+ iconColor={(this.props.textColor || "white") as ButtonColor}
116
+ prefix="fas"
117
+ onClick={() => this.props.onClick && this.props.onClick()}
118
+ />
119
+ )}
120
+ </Box>
121
+ </Box>
122
+ );
123
+ }
124
+ }
@@ -0,0 +1,40 @@
1
+ import {BlurView} from "@react-native-community/blur";
2
+ import React from "react";
3
+ import {Platform, View} from "react-native";
4
+
5
+ import {Box} from "./Box";
6
+ import {BlurBoxProps} from "./Common";
7
+
8
+ export class BlurBox extends React.Component<BlurBoxProps, {}> {
9
+ renderBlur(children: any) {
10
+ if (Platform.OS === "ios") {
11
+ return (
12
+ <BlurView blurType={this.props.blurType || "regular"} style={{borderRadius: 12}}>
13
+ {children}
14
+ </BlurView>
15
+ );
16
+ } else {
17
+ return (
18
+ <View style={{backgroundColor: "rgba(82, 82, 82, 0.7)", borderRadius: 10}}>{children}</View>
19
+ );
20
+ }
21
+ }
22
+
23
+ render() {
24
+ const {marginBottom, marginTop, margin, ...props} = this.props;
25
+ return (
26
+ <Box
27
+ margin={margin || 0}
28
+ marginBottom={marginBottom || 4}
29
+ marginTop={marginTop || 0}
30
+ width="100%"
31
+ >
32
+ {this.renderBlur(
33
+ <Box paddingX={4} {...props}>
34
+ {this.props.children}
35
+ </Box>
36
+ )}
37
+ </Box>
38
+ );
39
+ }
40
+ }
@@ -0,0 +1,31 @@
1
+ import React from "react";
2
+
3
+ import {Box} from "./Box";
4
+ import {BlurBoxProps} from "./Common";
5
+ import {mergeInlineStyles} from "./Utilities";
6
+
7
+ export class BlurBox extends React.Component<BlurBoxProps, {}> {
8
+ render() {
9
+ const {marginBottom, marginTop, margin, ...props} = this.props;
10
+
11
+ return (
12
+ <Box
13
+ {...this.props}
14
+ dangerouslySetInlineStyle={mergeInlineStyles(this.props.dangerouslySetInlineStyle, {
15
+ // filter: "blur(4px)",
16
+ backdropFilter: "blur(4px)",
17
+ backgroundColor: "#111",
18
+ opacity: 0.8,
19
+ borderRadius: 12,
20
+ })}
21
+ margin={margin || 0}
22
+ marginBottom={marginBottom || 4}
23
+ marginTop={marginTop || 0}
24
+ >
25
+ <Box paddingX={4} {...props}>
26
+ {this.props.children}
27
+ </Box>
28
+ </Box>
29
+ );
30
+ }
31
+ }
package/src/Body.tsx ADDED
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+ import {ActivityIndicator, KeyboardAvoidingView} from "react-native";
3
+
4
+ import {Box} from "./Box";
5
+ import {BodyProps} from "./Common";
6
+ import {Unifier} from "./Unifier";
7
+
8
+ export class Body extends React.Component<BodyProps, {}> {
9
+ renderBody() {
10
+ return (
11
+ <Box avoidKeyboard height="100%" scroll={this.props.scroll}>
12
+ <Box
13
+ height={this.props.height || "100%"}
14
+ padding={this.props.padding !== undefined ? this.props.padding : 5}
15
+ >
16
+ {this.props.loading === true && (
17
+ <ActivityIndicator color={Unifier.theme.darkGray} size="large" />
18
+ )}
19
+ {this.props.children}
20
+ </Box>
21
+ </Box>
22
+ );
23
+ }
24
+
25
+ render() {
26
+ if (this.props.avoidKeyboard === false) {
27
+ return this.renderBody();
28
+ } else {
29
+ return <KeyboardAvoidingView behavior="position">{this.renderBody()}</KeyboardAvoidingView>;
30
+ }
31
+ }
32
+ }