ferns-ui 0.36.4 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/Banner.d.ts +6 -16
  2. package/dist/Banner.js +52 -43
  3. package/dist/Banner.js.map +1 -1
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +1 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/useStoredState.d.ts +1 -0
  8. package/dist/useStoredState.js +33 -0
  9. package/dist/useStoredState.js.map +1 -0
  10. package/package.json +55 -56
  11. package/src/ActionSheet.tsx +1231 -0
  12. package/src/Avatar.tsx +317 -0
  13. package/src/Badge.tsx +65 -0
  14. package/src/Banner.tsx +149 -0
  15. package/src/BlurBox.native.tsx +40 -0
  16. package/src/BlurBox.tsx +31 -0
  17. package/src/Body.tsx +32 -0
  18. package/src/Box.tsx +308 -0
  19. package/src/Button.tsx +219 -0
  20. package/src/Card.tsx +23 -0
  21. package/src/CheckBox.tsx +118 -0
  22. package/src/Common.ts +2743 -0
  23. package/src/Constants.ts +53 -0
  24. package/src/CustomSelect.tsx +85 -0
  25. package/src/DateTimeActionSheet.tsx +409 -0
  26. package/src/DateTimeField.android.tsx +101 -0
  27. package/src/DateTimeField.ios.tsx +83 -0
  28. package/src/DateTimeField.tsx +69 -0
  29. package/src/DecimalRangeActionSheet.tsx +113 -0
  30. package/src/ErrorBoundary.tsx +37 -0
  31. package/src/ErrorPage.tsx +44 -0
  32. package/src/FernsProvider.tsx +21 -0
  33. package/src/Field.tsx +299 -0
  34. package/src/FieldWithLabels.tsx +36 -0
  35. package/src/FlatList.tsx +2 -0
  36. package/src/Form.tsx +182 -0
  37. package/src/HeaderButtons.tsx +107 -0
  38. package/src/Heading.tsx +53 -0
  39. package/src/HeightActionSheet.tsx +104 -0
  40. package/src/Hyperlink.tsx +181 -0
  41. package/src/Icon.tsx +24 -0
  42. package/src/IconButton.tsx +165 -0
  43. package/src/Image.tsx +50 -0
  44. package/src/ImageBackground.tsx +14 -0
  45. package/src/InfoTooltipButton.tsx +23 -0
  46. package/src/Layer.tsx +17 -0
  47. package/src/Link.tsx +17 -0
  48. package/src/Mask.tsx +21 -0
  49. package/src/MediaQuery.ts +46 -0
  50. package/src/Meta.tsx +9 -0
  51. package/src/Modal.tsx +248 -0
  52. package/src/ModalSheet.tsx +58 -0
  53. package/src/NumberPickerActionSheet.tsx +66 -0
  54. package/src/Page.tsx +133 -0
  55. package/src/Permissions.ts +44 -0
  56. package/src/PickerSelect.tsx +553 -0
  57. package/src/Pill.tsx +24 -0
  58. package/src/Pog.tsx +87 -0
  59. package/src/ProgressBar.tsx +55 -0
  60. package/src/ScrollView.tsx +2 -0
  61. package/src/SegmentedControl.tsx +102 -0
  62. package/src/SelectList.tsx +89 -0
  63. package/src/SideDrawer.tsx +62 -0
  64. package/src/Spinner.tsx +20 -0
  65. package/src/SplitPage.native.tsx +160 -0
  66. package/src/SplitPage.tsx +302 -0
  67. package/src/Switch.tsx +19 -0
  68. package/src/Table.tsx +87 -0
  69. package/src/TableHeader.tsx +36 -0
  70. package/src/TableHeaderCell.tsx +76 -0
  71. package/src/TableRow.tsx +87 -0
  72. package/src/TapToEdit.tsx +221 -0
  73. package/src/Text.tsx +131 -0
  74. package/src/TextArea.tsx +16 -0
  75. package/src/TextField.tsx +401 -0
  76. package/src/TextFieldNumberActionSheet.tsx +61 -0
  77. package/src/Toast.tsx +106 -0
  78. package/src/Tooltip.tsx +269 -0
  79. package/src/UnifiedScreens.ts +24 -0
  80. package/src/Unifier.ts +371 -0
  81. package/src/Utilities.tsx +159 -0
  82. package/src/WithLabel.tsx +57 -0
  83. package/src/dayjsExtended.ts +10 -0
  84. package/src/index.tsx +1347 -0
  85. package/src/polyfill.d.ts +11 -0
  86. package/src/tableContext.tsx +80 -0
  87. package/src/useStoredState.ts +39 -0
@@ -0,0 +1,165 @@
1
+ import React, {forwardRef, useState} from "react";
2
+ import {Platform, Pressable, View, ViewStyle} from "react-native";
3
+
4
+ import {
5
+ AllColors,
6
+ ButtonColor,
7
+ Color,
8
+ IconName,
9
+ IconPrefix,
10
+ IconSize,
11
+ iconSizeToNumber,
12
+ IndicatorDirection,
13
+ ThemeColor,
14
+ TooltipDirection,
15
+ } from "./Common";
16
+ import {Icon} from "./Icon";
17
+ import {Modal} from "./Modal";
18
+ import {Text} from "./Text";
19
+ import {Tooltip} from "./Tooltip";
20
+ import {Unifier} from "./Unifier";
21
+
22
+ export interface IconButtonProps {
23
+ prefix?: IconPrefix;
24
+ icon: IconName;
25
+ accessibilityLabel: string;
26
+ iconColor: "darkGray" | ButtonColor | ThemeColor | Color;
27
+ onClick: () => void;
28
+ size?: IconSize;
29
+ bgColor?: "transparent" | "transparentDarkGray" | "gray" | "lightGray" | "white"; // default transparent
30
+ disabled?: boolean;
31
+ selected?: boolean;
32
+ withConfirmation?: boolean;
33
+ confirmationText?: string;
34
+ confirmationHeading?: string;
35
+ tooltip?: {
36
+ text: string;
37
+ idealDirection?: TooltipDirection;
38
+ };
39
+ indicator?: boolean;
40
+ indicatorStyle?: {position: IndicatorDirection; color: AllColors};
41
+ }
42
+
43
+ // eslint-disable-next-line react/display-name
44
+ export const IconButton = forwardRef(
45
+ (
46
+ {
47
+ prefix,
48
+ icon,
49
+ iconColor,
50
+ onClick,
51
+ size,
52
+ bgColor = "transparent",
53
+ withConfirmation = false,
54
+ confirmationText = "Are you sure you want to continue?",
55
+ confirmationHeading = "Confirm",
56
+ tooltip,
57
+ indicator,
58
+ indicatorStyle = {position: "bottomRight", color: "primary"},
59
+ }: IconButtonProps,
60
+ ref
61
+ ) => {
62
+ const [showConfirmation, setShowConfirmation] = useState(false);
63
+
64
+ const opacity = 1;
65
+ let color: string;
66
+ if (bgColor === "transparentDarkGray") {
67
+ color = "rgba(0, 0, 0, 0.5)";
68
+ } else if (bgColor === "transparent" || !bgColor) {
69
+ color = "rgba(0, 0, 0, 0.0)";
70
+ } else {
71
+ color = Unifier.theme[bgColor];
72
+ }
73
+
74
+ const IndicatorPosition = {
75
+ bottomRight: {bottom: "20%", right: "20%"},
76
+ bottomLeft: {bottom: "20%", left: "20%"},
77
+ topRight: {top: "20%", right: "20%"},
78
+ topLeft: {top: "20%", left: "20%"},
79
+ };
80
+
81
+ const indicatorPosition = {position: "absolute", ...IndicatorPosition[indicatorStyle.position]};
82
+
83
+ const renderConfirmation = () => {
84
+ return (
85
+ <Modal
86
+ heading={confirmationHeading}
87
+ primaryButtonOnClick={() => {
88
+ onClick();
89
+ setShowConfirmation(false);
90
+ }}
91
+ primaryButtonText="Confirm"
92
+ secondaryButtonOnClick={(): void => setShowConfirmation(false)}
93
+ secondaryButtonText="Cancel"
94
+ size="sm"
95
+ visible={showConfirmation}
96
+ onDismiss={(): void => {
97
+ setShowConfirmation(false);
98
+ }}
99
+ >
100
+ <Text>{confirmationText}</Text>
101
+ </Modal>
102
+ );
103
+ };
104
+
105
+ function renderIconButton(): React.ReactElement {
106
+ return (
107
+ <>
108
+ <Pressable
109
+ ref={ref as any}
110
+ hitSlop={{top: 10, left: 10, bottom: 10, right: 10}}
111
+ style={{
112
+ opacity,
113
+ backgroundColor: color,
114
+ borderRadius: 100,
115
+ // paddingBottom: iconSizeToNumber(size) / 4,
116
+ // paddingTop: iconSizeToNumber(size) / 4,
117
+ // paddingLeft: iconSizeToNumber(size) / 2,
118
+ // paddingRight: iconSizeToNumber(size) / 2,
119
+ width: iconSizeToNumber(size) * 2.5,
120
+ height: iconSizeToNumber(size) * 2.5,
121
+ display: "flex",
122
+ justifyContent: "center",
123
+ alignItems: "center",
124
+ }}
125
+ onPress={() => {
126
+ Unifier.utils.haptic();
127
+ if (withConfirmation && !showConfirmation) {
128
+ setShowConfirmation(true);
129
+ } else if (onClick) {
130
+ onClick();
131
+ }
132
+ }}
133
+ >
134
+ <Icon color={iconColor} name={icon} prefix={prefix || "fas"} size={size} />
135
+ {indicator && (
136
+ <View style={indicatorPosition as ViewStyle}>
137
+ <Icon
138
+ color={indicatorStyle.color}
139
+ name="circle"
140
+ prefix={prefix || "fas"}
141
+ size="sm"
142
+ />
143
+ </View>
144
+ )}
145
+ </Pressable>
146
+
147
+ {Boolean(withConfirmation) && renderConfirmation()}
148
+ </>
149
+ );
150
+ }
151
+
152
+ // Only add for web. This doesn't make much sense for mobile, since the action would be performed for the button
153
+ // as well as the tooltip appearing.
154
+ // TODO: Add tooltip info button next to the icon button on mobile.
155
+ if (tooltip && Platform.OS === "web") {
156
+ return (
157
+ <Tooltip idealDirection={tooltip.idealDirection} text={tooltip.text}>
158
+ {renderIconButton()}
159
+ </Tooltip>
160
+ );
161
+ } else {
162
+ return renderIconButton();
163
+ }
164
+ }
165
+ );
package/src/Image.tsx ADDED
@@ -0,0 +1,50 @@
1
+ import React from "react";
2
+ import {Dimensions, Image as NativeImage} from "react-native";
3
+
4
+ import {Box} from "./Box";
5
+ import {ImageProps} from "./Common";
6
+ const {width: DEVICE_WIDTH} = Dimensions.get("window");
7
+
8
+ export class Image extends React.Component<ImageProps, {}> {
9
+ resizeMode = (fit?: "cover" | "contain" | "none") => {
10
+ if (!fit || fit === "none") {
11
+ return undefined;
12
+ } else {
13
+ return fit;
14
+ }
15
+ };
16
+
17
+ width = () => {
18
+ if (this.props.naturalWidth) {
19
+ return this.props.naturalWidth;
20
+ } else if (this.props.fullWidth) {
21
+ return DEVICE_WIDTH;
22
+ }
23
+ throw new Error("Width required for Image");
24
+ };
25
+
26
+ height = () => {
27
+ if (this.props.naturalWidth) {
28
+ return this.props.naturalWidth;
29
+ }
30
+ return this.width() * (9 / 16);
31
+ };
32
+
33
+ render() {
34
+ return (
35
+ <Box color={this.props.color}>
36
+ <NativeImage
37
+ resizeMode={this.resizeMode(this.props.fit)}
38
+ source={{uri: this.props.src, cache: "force-cache"}}
39
+ style={{
40
+ height: this.height(),
41
+ width: this.width(),
42
+ maxHeight: this.props.maxHeight,
43
+ maxWidth: this.props.maxWidth,
44
+ ...this.props.style,
45
+ }}
46
+ />
47
+ </Box>
48
+ );
49
+ }
50
+ }
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ import {ImageBackground as ImageBackgroundNative} from "react-native";
3
+
4
+ interface ImageBackgroundProps {
5
+ children?: any;
6
+ style?: any;
7
+ source: any;
8
+ }
9
+
10
+ export class ImageBackground extends React.Component<ImageBackgroundProps, {}> {
11
+ render() {
12
+ return <ImageBackgroundNative {...this.props} />;
13
+ }
14
+ }
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+
3
+ import {IconSize} from "./Common";
4
+ import {IconButton} from "./IconButton";
5
+
6
+ interface InfoTooltipButtonProps {
7
+ text: string;
8
+ size?: IconSize;
9
+ }
10
+
11
+ export function InfoTooltipButton({text, size}: InfoTooltipButtonProps): React.ReactElement {
12
+ return (
13
+ <IconButton
14
+ accessibilityLabel="info"
15
+ bgColor="transparent"
16
+ icon="exclamation"
17
+ iconColor="blue"
18
+ size={size}
19
+ tooltip={{text}}
20
+ onClick={() => {}}
21
+ />
22
+ );
23
+ }
package/src/Layer.tsx ADDED
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+
3
+ import {LayerProps} from "./Common";
4
+
5
+ interface LayerState {}
6
+
7
+ // TODO: Flesh out for native.
8
+ export class Layer extends React.Component<LayerProps, LayerState> {
9
+ constructor(props: LayerProps) {
10
+ super(props);
11
+ this.state = {};
12
+ }
13
+
14
+ render() {
15
+ return this.props.children;
16
+ }
17
+ }
package/src/Link.tsx ADDED
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+
3
+ import {LinkProps} from "./Common";
4
+ import {Text} from "./Text";
5
+
6
+ interface LinkState {}
7
+
8
+ export class Link extends React.Component<LinkProps, LinkState> {
9
+ constructor(props: LinkProps) {
10
+ super(props);
11
+ this.state = {};
12
+ }
13
+
14
+ render() {
15
+ return <Text>{this.props.children}</Text>;
16
+ }
17
+ }
package/src/Mask.tsx ADDED
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import {View} from "react-native";
3
+
4
+ import {MaskProps, ReactChildren} from "./Common";
5
+
6
+ export function Mask(props: MaskProps): ReactChildren {
7
+ if (props.shape === "rounded") {
8
+ return <View style={{overflow: "hidden", borderRadius: 12}}>{props.children}</View>;
9
+ } else if (props.shape === "circle") {
10
+ return <View style={{overflow: "hidden", borderRadius: 1000}}>{props.children}</View>;
11
+ }
12
+ if (props.rounding) {
13
+ const rounding = props.rounding === "circle" ? 100 : props.rounding;
14
+ // Subtract 1 from rounding because of some very odd rendering.
15
+ return (
16
+ <View style={{borderRadius: (rounding - 1) * 4, overflow: "visible"}}>{props.children}</View>
17
+ );
18
+ } else {
19
+ return props.children || null;
20
+ }
21
+ }
@@ -0,0 +1,46 @@
1
+ import {Dimensions} from "react-native";
2
+
3
+ export function mediaQuery(): "xs" | "sm" | "md" | "lg" {
4
+ const width = Dimensions.get("window").width;
5
+ if (width < 576) {
6
+ return "xs";
7
+ } else if (width < 768) {
8
+ return "sm";
9
+ } else if (width < 1312) {
10
+ return "md";
11
+ } else {
12
+ return "lg";
13
+ }
14
+ }
15
+
16
+ export function mediaQueryLargerThan(size: "xs" | "sm" | "md" | "lg"): boolean {
17
+ const media = mediaQuery();
18
+ if (size === "xs") {
19
+ return true;
20
+ } else if (size === "sm") {
21
+ return ["sm", "md", "lg"].includes(media);
22
+ } else if (size === "md") {
23
+ return ["md", "lg"].includes(media);
24
+ } else if (size === "lg") {
25
+ return ["lg"].includes(media);
26
+ }
27
+ return false;
28
+ }
29
+
30
+ export function mediaQuerySmallerThan(size: "xs" | "sm" | "md" | "lg"): boolean {
31
+ const media = mediaQuery();
32
+ if (size === "lg") {
33
+ return true;
34
+ } else if (size === "md") {
35
+ return ["xs", "sm", "md"].includes(media);
36
+ } else if (size === "sm") {
37
+ return ["xs", "sm"].includes(media);
38
+ } else if (size === "xs") {
39
+ return ["xs"].includes(media);
40
+ }
41
+ return false;
42
+ }
43
+
44
+ export function isMobileDevice(): boolean {
45
+ return !mediaQueryLargerThan("sm");
46
+ }
package/src/Meta.tsx ADDED
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+
3
+ import {MetaProps} from "./Common";
4
+
5
+ export class Meta extends React.Component<MetaProps, {}> {
6
+ render() {
7
+ return this.props.children || null;
8
+ }
9
+ }
package/src/Modal.tsx ADDED
@@ -0,0 +1,248 @@
1
+ import React, {useEffect, useRef} from "react";
2
+ import {Dimensions, Modal as RNModal} from "react-native";
3
+ import ActionSheet, {ActionSheetRef} from "react-native-actions-sheet";
4
+
5
+ import {Box} from "./Box";
6
+ import {Button} from "./Button";
7
+ import {Heading} from "./Heading";
8
+ import {IconButton} from "./IconButton";
9
+ import {isMobileDevice} from "./MediaQuery";
10
+ import {Text} from "./Text";
11
+
12
+ interface ModalProps {
13
+ onDismiss: () => void;
14
+ visible: boolean;
15
+ // Alignment of the header. Default is "center".
16
+ align?: "center" | "start";
17
+ // Element to render in the middle part of the modal.
18
+ children?: React.ReactElement;
19
+ // Element to render in the bottom of the modal. This takes precedence over primaryButton and secondaryButton.
20
+ footer?: React.ReactElement;
21
+ heading?: string;
22
+ size?: "sm" | "md" | "lg";
23
+ subHeading?: string;
24
+ // Renders a primary colored button all the way to the right in the footer, if no footer prop is provided.
25
+ primaryButtonText?: string;
26
+ primaryButtonOnClick?: (value?: any) => void;
27
+ primaryButtonDisabled?: boolean;
28
+ // Renders a gray button to the left of the primary button in the footer, if no footer prop is provided.
29
+ // Requires primaryButtonText to be defined, but is not required itself.
30
+ secondaryButtonText?: string;
31
+ secondaryButtonOnClick?: (value?: any) => void;
32
+ // Whether to show a close button in the upper left of modals or action sheets.
33
+ showClose?: boolean;
34
+ }
35
+
36
+ export const Modal = ({
37
+ onDismiss,
38
+ visible,
39
+ align = "center",
40
+ children,
41
+ footer,
42
+ heading,
43
+ size,
44
+ subHeading,
45
+ primaryButtonText,
46
+ primaryButtonOnClick,
47
+ primaryButtonDisabled = false,
48
+ secondaryButtonText,
49
+ secondaryButtonOnClick,
50
+ showClose = false,
51
+ }: ModalProps): React.ReactElement => {
52
+ const actionSheetRef = useRef<ActionSheetRef>(null);
53
+
54
+ if (subHeading && !heading) {
55
+ throw new Error("Cannot render Modal with subHeading and no heading");
56
+ }
57
+ if (!footer && !primaryButtonText && !secondaryButtonText && !showClose) {
58
+ throw new Error(
59
+ "Cannot render Modal without footer, primaryButtonText, secondaryButtonText, or showClose"
60
+ );
61
+ }
62
+
63
+ let sizePx: string | number = 540;
64
+ if (size === "md") {
65
+ sizePx = 720;
66
+ } else if (size === "lg") {
67
+ sizePx = 900;
68
+ }
69
+
70
+ // Adjust size for small screens
71
+ if (sizePx > Dimensions.get("window").width) {
72
+ sizePx = "90%";
73
+ }
74
+
75
+ // Modal uses a visible prop, but ActionSheet uses a setModalVisible method on a reference.
76
+ // Open the action sheet ref when the visible prop changes.
77
+ useEffect(() => {
78
+ if (actionSheetRef.current) {
79
+ actionSheetRef.current.setModalVisible(visible);
80
+ }
81
+ }, [visible, actionSheetRef]);
82
+
83
+ const renderClose = (): React.ReactElement | null => {
84
+ if (!showClose) {
85
+ return null;
86
+ }
87
+ return (
88
+ <IconButton
89
+ accessibilityLabel="close"
90
+ bgColor="white"
91
+ icon="times"
92
+ iconColor="darkGray"
93
+ onClick={() => onDismiss()}
94
+ />
95
+ );
96
+ };
97
+
98
+ const renderModalHeader = (): React.ReactElement => {
99
+ return (
100
+ <Box direction="row" padding={3} width="100%">
101
+ <Box width={40}>{renderClose()}</Box>
102
+ <Box direction="column" flex="grow">
103
+ <Heading align={align === "center" ? "center" : undefined} size="sm">
104
+ {heading}
105
+ </Heading>
106
+ {Boolean(subHeading) && (
107
+ <Box paddingY={2}>
108
+ <Text align={align === "center" ? "center" : undefined}>{subHeading}</Text>
109
+ </Box>
110
+ )}
111
+ </Box>
112
+ <Box width={40} />
113
+ </Box>
114
+ );
115
+ };
116
+
117
+ const renderModalFooter = (): React.ReactElement | null => {
118
+ if (footer) {
119
+ return footer;
120
+ }
121
+ return (
122
+ <Box direction="row" justifyContent="end" width="100%">
123
+ {Boolean(secondaryButtonText) && (
124
+ <Box marginRight={4} minWidth={120}>
125
+ <Button
126
+ color="gray"
127
+ text={secondaryButtonText ?? ""}
128
+ onClick={secondaryButtonOnClick}
129
+ />
130
+ </Box>
131
+ )}
132
+ <Box minWidth={120}>
133
+ <Button
134
+ color="primary"
135
+ disabled={primaryButtonDisabled}
136
+ text={primaryButtonText ?? ""}
137
+ onClick={primaryButtonOnClick}
138
+ />
139
+ </Box>
140
+ </Box>
141
+ );
142
+ };
143
+
144
+ const renderModal = (): React.ReactElement => {
145
+ return (
146
+ <RNModal animationType="slide" transparent visible={visible} onRequestClose={onDismiss}>
147
+ <Box
148
+ alignItems="center"
149
+ alignSelf="center"
150
+ color="white"
151
+ dangerouslySetInlineStyle={{
152
+ __style: {
153
+ zIndex: 1,
154
+ shadowColor: "#999",
155
+ shadowOffset: {
156
+ width: 4,
157
+ height: 6,
158
+ },
159
+ shadowRadius: 4,
160
+ shadowOpacity: 1.0,
161
+ elevation: 8,
162
+ },
163
+ }}
164
+ direction="column"
165
+ justifyContent="center"
166
+ marginTop={12}
167
+ maxWidth={sizePx}
168
+ minWidth={300}
169
+ paddingX={8}
170
+ paddingY={2}
171
+ rounding={6}
172
+ shadow
173
+ width={sizePx}
174
+ >
175
+ <Box marginBottom={6} width="100%">
176
+ {renderModalHeader()}
177
+ <Box paddingY={4}>{children}</Box>
178
+ <Box paddingY={4}>{renderModalFooter()}</Box>
179
+ </Box>
180
+ </Box>
181
+ </RNModal>
182
+ );
183
+ };
184
+
185
+ const renderActionSheet = (): React.ReactElement => {
186
+ return (
187
+ <ActionSheet ref={actionSheetRef} onClose={onDismiss}>
188
+ <Box direction="row" marginBottom={2} paddingX={2} paddingY={2} width="100%">
189
+ <Box marginRight={4}>
190
+ {Boolean(showClose) && (
191
+ <IconButton
192
+ accessibilityLabel="close"
193
+ bgColor="white"
194
+ icon="times"
195
+ iconColor="darkGray"
196
+ size="lg"
197
+ onClick={() => onDismiss()}
198
+ />
199
+ )}
200
+ {Boolean(secondaryButtonText) && (
201
+ <Button
202
+ color="darkGray"
203
+ inline
204
+ size="lg"
205
+ text={secondaryButtonText ?? ""}
206
+ type="ghost"
207
+ onClick={secondaryButtonOnClick}
208
+ />
209
+ )}
210
+ </Box>
211
+ <Box alignItems="center" direction="column" flex="grow" justifyContent="center">
212
+ <Heading align={align === "center" ? "center" : undefined} size="sm">
213
+ {heading}
214
+ </Heading>
215
+ {Boolean(subHeading) && (
216
+ <Box paddingY={2}>
217
+ <Text align={align === "center" ? "center" : undefined}>{subHeading}</Text>
218
+ </Box>
219
+ )}
220
+ </Box>
221
+
222
+ <Box alignSelf="start" height="100%" justifyContent="start">
223
+ {Boolean(primaryButtonText) && (
224
+ <Button
225
+ color="primary"
226
+ disabled={primaryButtonDisabled}
227
+ inline
228
+ size="md"
229
+ text={primaryButtonText!}
230
+ type="ghost"
231
+ onClick={primaryButtonOnClick}
232
+ />
233
+ )}
234
+ </Box>
235
+ </Box>
236
+ <Box marginBottom={12} paddingX={4}>
237
+ {children}
238
+ </Box>
239
+ </ActionSheet>
240
+ );
241
+ };
242
+
243
+ if (isMobileDevice()) {
244
+ return renderActionSheet();
245
+ } else {
246
+ return renderModal();
247
+ }
248
+ };
@@ -0,0 +1,58 @@
1
+ /* eslint-disable react/display-name */
2
+ import React, {forwardRef, useEffect, useRef} from "react";
3
+ import {Animated} from "react-native";
4
+ import {Modalize} from "react-native-modalize";
5
+ import {Portal} from "react-native-portalize";
6
+
7
+ export const useCombinedRefs = (...refs: any) => {
8
+ const targetRef = useRef();
9
+
10
+ useEffect(() => {
11
+ refs.forEach((ref: any) => {
12
+ if (!ref) {
13
+ return;
14
+ }
15
+
16
+ if (typeof ref === "function") {
17
+ ref(targetRef.current);
18
+ } else {
19
+ ref.current = targetRef.current;
20
+ }
21
+ });
22
+ }, [refs]);
23
+
24
+ return targetRef;
25
+ };
26
+
27
+ interface Props {
28
+ children: any;
29
+ }
30
+
31
+ export const SimpleContent = forwardRef((props: Props, ref) => {
32
+ const modalizeRef = useRef(null);
33
+ const combinedRef = useCombinedRefs(ref, modalizeRef);
34
+ const animated = useRef(new Animated.Value(0)).current;
35
+
36
+ // const renderHeader = () => (
37
+ // <Box paddingY={4} marginTop={4} marginBottom={4}>
38
+ // <Text>50 users online</Text>
39
+ // </Box>
40
+ // );
41
+
42
+ return (
43
+ <Portal>
44
+ <Modalize
45
+ // HeaderComponent={renderHeader}
46
+ ref={combinedRef}
47
+ adjustToContentHeight
48
+ panGestureAnimatedValue={animated}
49
+ scrollViewProps={{
50
+ showsVerticalScrollIndicator: false,
51
+ stickyHeaderIndices: [0],
52
+ }}
53
+ >
54
+ {props.children}
55
+ </Modalize>
56
+ </Portal>
57
+ );
58
+ });