@shoutem/ui 7.1.0-beta.0 → 7.1.0-beta.10

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.
@@ -1,183 +1,69 @@
1
- import React, { PureComponent } from 'react';
2
- import { Animated, Dimensions, TouchableOpacity } from 'react-native';
3
- import autoBindReact from 'auto-bind/react';
1
+ import React, { useEffect, useMemo, useRef } from 'react';
2
+ import ActionSheetNative from 'react-native-actions-sheet';
4
3
  import _ from 'lodash';
5
4
  import PropTypes from 'prop-types';
6
5
  import { connectStyle } from '@shoutem/theme';
7
- import { IPHONE_X_HOME_INDICATOR_PADDING } from '../../const';
8
- import { Device } from '../../helpers';
9
6
  import { View } from '../View';
10
7
  import ActionSheetOption, { optionPropType } from './ActionSheetOption';
11
8
 
12
- const { height: windowHeight } = Dimensions.get('window');
13
-
14
- function getContentOffset(contentHeight = windowHeight) {
15
- if (Device.isIphoneXR || Device.isIphoneX) {
16
- return contentHeight + IPHONE_X_HOME_INDICATOR_PADDING;
17
- }
18
-
19
- return contentHeight;
20
- }
21
-
22
- const AnimatedTouchable = Animated.createAnimatedComponent(TouchableOpacity);
23
-
24
- class ActionSheet extends PureComponent {
25
- static getDerivedStateFromProps(props, state) {
26
- const { active } = props;
27
- const { visible } = state;
28
-
29
- if (active !== visible) {
30
- return { visible: active };
9
+ const ActionSheet = ({
10
+ active = false,
11
+ cancelOptions = undefined,
12
+ confirmOptions = undefined,
13
+ style,
14
+ onDismiss = undefined,
15
+ ...otherProps
16
+ }) => {
17
+ const actionSheetRef = useRef(null);
18
+
19
+ const hasConfirmOptions = useMemo(() => !_.isEmpty(confirmOptions), [
20
+ confirmOptions,
21
+ ]);
22
+ const hasCancelOptions = useMemo(() => !_.isEmpty(cancelOptions), [
23
+ cancelOptions,
24
+ ]);
25
+
26
+ useEffect(() => {
27
+ if (active) {
28
+ actionSheetRef.current?.show();
29
+ } else {
30
+ actionSheetRef.current?.hide();
31
31
  }
32
-
33
- return null;
34
- }
35
-
36
- constructor(props) {
37
- super(props);
38
-
39
- autoBindReact(this);
40
-
41
- this.state = {
42
- opacity: new Animated.Value(0),
43
- yPos: new Animated.Value(0),
44
- visible: false,
45
- };
46
- }
47
-
48
- componentDidUpdate(prevProps) {
49
- const { active } = this.props;
50
- const { active: prevActive } = prevProps;
51
-
52
- if (active && !prevActive) {
53
- this.handleEntryAnimation();
54
- }
55
-
56
- if (!active && prevActive) {
57
- this.handleExitAnimation();
58
- }
59
- }
60
-
61
- hideComponent() {
62
- this.setState({ visible: false });
63
- }
64
-
65
- handleContentLayout(event) {
66
- const height = _.get(event, 'nativeEvent.layout.height', 0);
67
-
68
- this.setState({ height });
69
- }
70
-
71
- handleEntryAnimation() {
72
- const { yPos, opacity } = this.state;
73
-
74
- const toValue = 1;
75
-
76
- Animated.parallel([
77
- Animated.spring(yPos, {
78
- toValue,
79
- useNativeDriver: true,
80
- }),
81
- Animated.timing(opacity, {
82
- toValue,
83
- useNativeDriver: false,
84
- }),
85
- ]).start();
86
- }
87
-
88
- handleExitAnimation() {
89
- const { yPos, opacity } = this.state;
90
-
91
- const toValue = 0;
92
-
93
- Animated.parallel([
94
- Animated.spring(yPos, {
95
- toValue,
96
- useNativeDriver: true,
97
- }),
98
- Animated.timing(opacity, {
99
- toValue,
100
- delay: 100,
101
- duration: 150,
102
- useNativeDriver: false,
103
- }),
104
- ]).start(this.hideComponent);
105
- }
106
-
107
- handleInactiveAreaPress() {
108
- const { onDismiss } = this.props;
109
-
110
- if (onDismiss) {
111
- onDismiss();
112
- }
113
- }
114
-
115
- render() {
116
- const { confirmOptions, cancelOptions, style } = this.props;
117
- const { opacity, yPos, visible, height } = this.state;
118
-
119
- const hasConfirmOptions = !_.isEmpty(confirmOptions);
120
- const hasCancelOptions = !_.isEmpty(cancelOptions);
121
-
122
- if (!visible || (!hasConfirmOptions && !hasCancelOptions)) {
123
- return null;
124
- }
125
-
126
- return (
127
- <AnimatedTouchable
128
- activeOpacity={1}
129
- onPress={this.handleInactiveAreaPress}
130
- style={[
131
- style.container,
132
- {
133
- backgroundColor: opacity.interpolate({
134
- inputRange: [0, 1],
135
- outputRange: ['rgba(0, 0, 0, 0)', 'rgba(0, 0, 0, 0.3)'],
136
- }),
137
- },
138
- ]}
139
- >
140
- <Animated.View
141
- onLayout={this.handleContentLayout}
142
- style={[
143
- style.contentContainer,
144
- {
145
- transform: [
146
- {
147
- translateY: yPos.interpolate({
148
- inputRange: [0, 1],
149
- outputRange: [getContentOffset(height), 0],
150
- }),
151
- },
152
- ],
153
- },
154
- ]}
155
- >
156
- {hasConfirmOptions && (
32
+ }, [active]);
33
+
34
+ return (
35
+ <ActionSheetNative
36
+ gestureEnabled
37
+ ref={actionSheetRef}
38
+ onClose={onDismiss}
39
+ containerStyle={style.container}
40
+ {...otherProps}
41
+ >
42
+ <View style={style.contentContainer}>
43
+ {hasConfirmOptions && (
44
+ <View style={style.segmentContainer}>
45
+ {_.map(confirmOptions, option => (
46
+ <ActionSheetOption key={option.title} option={option} />
47
+ ))}
48
+ </View>
49
+ )}
50
+ {hasCancelOptions && (
51
+ <View styleName="md-gutter-top">
157
52
  <View style={style.segmentContainer}>
158
- {_.map(confirmOptions, option => (
159
- <ActionSheetOption key={option.title} option={option} />
53
+ {_.map(cancelOptions, option => (
54
+ <ActionSheetOption
55
+ key={option.title}
56
+ cancelOption
57
+ option={option}
58
+ />
160
59
  ))}
161
60
  </View>
162
- )}
163
- {hasCancelOptions && (
164
- <View styleName="md-gutter-top">
165
- <View style={style.segmentContainer}>
166
- {_.map(cancelOptions, option => (
167
- <ActionSheetOption
168
- key={option.title}
169
- cancelOption
170
- option={option}
171
- />
172
- ))}
173
- </View>
174
- </View>
175
- )}
176
- </Animated.View>
177
- </AnimatedTouchable>
178
- );
179
- }
180
- }
61
+ </View>
62
+ )}
63
+ </View>
64
+ </ActionSheetNative>
65
+ );
66
+ };
181
67
 
182
68
  ActionSheet.propTypes = {
183
69
  style: PropTypes.object.isRequired,
@@ -37,9 +37,15 @@ class EmptyListImage extends PureComponent {
37
37
  } = this.props;
38
38
  const EmptyStateImage = this.resolveImage();
39
39
 
40
+ // SVG cannot receive array of style elements on Web
41
+ const resolvedStyle = {
42
+ ...style.image,
43
+ ...imageStyle,
44
+ };
45
+
40
46
  return (
41
47
  <View styleName="vertical h-center ">
42
- <EmptyStateImage style={[style.image, imageStyle]} />
48
+ <EmptyStateImage style={resolvedStyle} />
43
49
  <Title styleName="title" style={[style.title, titleStyle]}>
44
50
  {title}
45
51
  </Title>
@@ -1,5 +1,5 @@
1
1
  import React, { PureComponent } from 'react';
2
- import { InteractionManager, LayoutAnimation, ScrollView } from 'react-native';
2
+ import { InteractionManager, LayoutAnimation, ScrollView, Platform } from 'react-native';
3
3
  import autoBindReact from 'auto-bind/react';
4
4
  import _ from 'lodash';
5
5
  import PropTypes from 'prop-types';
@@ -219,7 +219,14 @@ class HorizontalPager extends PureComponent {
219
219
  );
220
220
 
221
221
  return (
222
- <View key={pageIndex} style={{ width: containerWidth }}>
222
+ <View
223
+ key={pageIndex}
224
+ style={{
225
+ width: containerWidth,
226
+ ...(Platform.OS === 'web' && {
227
+ height: '100%',
228
+ })
229
+ }}>
223
230
  {pageContent}
224
231
  </View>
225
232
  );
@@ -1,5 +1,6 @@
1
1
  import React, { PureComponent } from 'react';
2
2
  import { Image, Modal, TouchableOpacity, View } from 'react-native';
3
+ import { ImagePropTypes } from 'deprecated-react-native-prop-types';
3
4
  import autoBindReact from 'auto-bind/react';
4
5
  import PropTypes from 'prop-types';
5
6
  import { makeZoomable } from '@shoutem/animation';
@@ -84,7 +85,7 @@ class ImagePreview extends PureComponent {
84
85
  ImagePreview.propTypes = {
85
86
  style: PropTypes.object.isRequired,
86
87
  height: PropTypes.number,
87
- source: Image.propTypes.source,
88
+ source: ImagePropTypes.source,
88
89
  width: PropTypes.number,
89
90
  };
90
91
 
@@ -7,6 +7,7 @@ import {
7
7
  StatusBar,
8
8
  View,
9
9
  } from 'react-native';
10
+ import { RefreshControl as WebRefreshControl } from 'react-native-web-refresh-control';
10
11
  import autoBindReact from 'auto-bind/react';
11
12
  import _ from 'lodash';
12
13
  import PropTypes from 'prop-types';
@@ -37,15 +38,18 @@ class ListView extends PureComponent {
37
38
  static getDerivedStateFromProps(props, state) {
38
39
  const isLoading = props.loading;
39
40
 
40
- if (isLoading) {
41
- if (state.status !== Status.IDLE) {
42
- // We are already in a loading status
43
- return state;
44
- }
45
-
41
+ // If data is loading but no status updates were triggered, set status to loading.
42
+ if (isLoading && state.status === Status.IDLE) {
46
43
  return { status: Status.LOADING };
47
44
  }
48
- return { status: Status.IDLE };
45
+
46
+ // If loading is done, set status back to idle.
47
+ if (!isLoading && state.status === Status.LOADING) {
48
+ return { status: Status.IDLE}
49
+ }
50
+
51
+ // Return all other statuses as they are.
52
+ return { status: state.status ?? Status.IDLE };
49
53
  }
50
54
 
51
55
  constructor(props, context) {
@@ -78,6 +82,14 @@ class ListView extends PureComponent {
78
82
 
79
83
  if (onRefresh) {
80
84
  onRefresh();
85
+
86
+ // Adding timeout, until onRefresh becomes async function. Then, we'll be able to know when
87
+ // refresh is done and set status back to idle.
88
+ setTimeout(() => {
89
+ this.setState({
90
+ status: Status.IDLE,
91
+ });
92
+ }, 500);
81
93
  }
82
94
  }
83
95
 
@@ -302,6 +314,17 @@ class ListView extends PureComponent {
302
314
  };
303
315
  delete refreshControlStyle.tintColor;
304
316
 
317
+ if (Platform.OS === 'web') {
318
+ return (
319
+ <WebRefreshControl
320
+ onRefresh={this.onRefresh}
321
+ refreshing={status === Status.REFRESHING}
322
+ tintColor={style.refreshControl.tintColor}
323
+ style={refreshControlStyle}
324
+ />
325
+ );
326
+ }
327
+
305
328
  return (
306
329
  <RefreshControl
307
330
  onRefresh={this.onRefresh}
@@ -3,6 +3,7 @@ import { Platform, Share } from 'react-native';
3
3
  import autoBindReact from 'auto-bind/react';
4
4
  import PropTypes from 'prop-types';
5
5
  import { connectStyle } from '@shoutem/theme';
6
+ import { unavailableInWeb } from '../services';
6
7
  import { Button } from './Button';
7
8
  import { Icon } from './Icon';
8
9
 
@@ -41,7 +42,7 @@ class ShareButton extends PureComponent {
41
42
  }
42
43
 
43
44
  return (
44
- <Button onPress={this.onShare} {...otherProps}>
45
+ <Button onPress={unavailableInWeb(this.onShare)} {...otherProps}>
45
46
  <Icon
46
47
  name={Platform.OS === 'ios' ? 'share' : 'share-android'}
47
48
  animationName={animationName}
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable react/no-multi-comp */
2
2
  import React, { forwardRef, PureComponent } from 'react';
3
- import { TextInput as RNTextInput } from 'react-native';
3
+ import { TextInput as RNTextInput, Platform } from 'react-native';
4
4
  import autoBindReact from 'auto-bind/react';
5
5
  import { TextInputPropTypes } from 'deprecated-react-native-prop-types';
6
6
  import PropTypes from 'prop-types';
@@ -60,14 +60,16 @@ class TextInput extends PureComponent {
60
60
  withoutBorder,
61
61
  wiggleAnimation,
62
62
  errorText,
63
+ height,
63
64
  ...otherStyle
64
65
  } = style;
65
66
 
67
+ const containerStyle = Platform.OS === "web" ? { height } : undefined;
66
68
  const hasBorder = (isFocused && highlightOnFocus) || !!errorMessage;
67
69
  const startErrorAnimation = animate && !!errorMessage;
68
70
 
69
71
  return (
70
- <View>
72
+ <View style={containerStyle}>
71
73
  <Wiggle style={wiggleAnimation} startAnimation={startErrorAnimation}>
72
74
  <RNTextInput
73
75
  {...otherProps}
@@ -80,6 +82,7 @@ class TextInput extends PureComponent {
80
82
  ...(hasBorder ? withBorder : withoutBorder),
81
83
  ...(errorMessage ? errorBorderColor : {}),
82
84
  ...otherStyle,
85
+ height: 50,
83
86
  }}
84
87
  ref={forwardedRef}
85
88
  />
@@ -44,7 +44,7 @@ class Video extends PureComponent {
44
44
  }
45
45
 
46
46
  render() {
47
- const { width, height, style, poster } = this.props;
47
+ const { width, height = '100%', style, poster } = this.props;
48
48
 
49
49
  return (
50
50
  <View style={style.container}>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shoutem/ui",
3
- "version": "7.1.0-beta.0",
3
+ "version": "7.1.0-beta.10",
4
4
  "description": "Styleable set of components for React Native applications",
5
5
  "scripts": {
6
6
  "lint": "eslint .",
@@ -15,7 +15,7 @@
15
15
  "@native-html/table-plugin": "5.3.1",
16
16
  "@openspacelabs/react-native-zoomable-view": "2.0.4",
17
17
  "@react-native-community/datetimepicker": "6.2.0",
18
- "@shoutem/animation": "~0.15.0",
18
+ "@shoutem/animation": "0.17.0-beta.0",
19
19
  "@shoutem/eslint-config-react": "~1.0.6",
20
20
  "@shoutem/theme": "~0.13.2",
21
21
  "auto-bind": "4.0.0",
@@ -35,18 +35,21 @@
35
35
  "react-native-linear-gradient": "2.8.3",
36
36
  "react-native-modal": "13.0.1",
37
37
  "react-native-render-html": "6.3.4",
38
- "react-native-svg": "12.3.0",
38
+ "react-native-svg": "15.4.0",
39
39
  "react-native-svg-transformer": "1.3.0",
40
40
  "react-native-toast-message": "2.1.5",
41
41
  "react-native-vimeo-iframe": "^1.2.1",
42
+ "react-native-web-refresh-control": "1.1.2",
42
43
  "react-native-webview": "11.23.0",
43
44
  "react-native-youtube-iframe": "2.2.2",
45
+ "react-native-actions-sheet": "0.9.3",
44
46
  "stream": "0.0.2",
45
47
  "tinycolor2": "1.4.1"
46
48
  },
47
49
  "peerDependencies": {
48
50
  "react": ">=16.9.0",
49
- "react-native": ">=0.61.5"
51
+ "react-native": ">=0.61.5",
52
+ "react-native-gesture-handler": "*"
50
53
  },
51
54
  "devDependencies": {
52
55
  "@babel/core": "7.16.7",
package/services/index.js CHANGED
@@ -1,6 +1,8 @@
1
+ export { isAndroid, isIos, isWeb } from './platform';
2
+ export { unavailableInWeb } from './unavailableInWeb';
1
3
  export {
2
- ThemeVariableResolver,
4
+ createScopedResolver,
3
5
  defaultResolver,
4
6
  resolveVariable,
5
- createScopedResolver,
7
+ ThemeVariableResolver,
6
8
  } from './variableResolver';
@@ -0,0 +1,5 @@
1
+ import { Platform } from 'react-native';
2
+
3
+ export const isAndroid = Platform.OS === 'android';
4
+ export const isIos = Platform.OS === 'ios';
5
+ export const isWeb = Platform.OS === 'web';
@@ -0,0 +1,29 @@
1
+ import { Platform } from 'react-native';
2
+ import { Toast } from '@shoutem/ui';
3
+ import _ from 'lodash';
4
+
5
+ const isWeb = Platform.OS === 'web';
6
+
7
+ export const unavailableInWeb = callback => {
8
+ if (!isWeb) {
9
+ return callback;
10
+ }
11
+
12
+ // If callback is undefined, show toast immediatelly.
13
+ if (_.isUndefined(callback)) {
14
+ Toast.showInfo({
15
+ title: 'Feature currently unavailable',
16
+ message:
17
+ 'This feature is currently unavailable in Web Preview. For better preview experience download Disclose app.',
18
+ });
19
+ }
20
+
21
+ // Otherwise, return callback to be executed on user action.
22
+
23
+ return () =>
24
+ Toast.showInfo({
25
+ title: 'Feature currently unavailable',
26
+ message:
27
+ 'This feature is currently unavailable in Web Preview. For better preview experience download Disclose app.',
28
+ });
29
+ };
package/theme.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Dimensions, Platform, StyleSheet } from 'react-native';
1
+ import { Dimensions, StyleSheet } from 'react-native';
2
2
  import {
3
3
  changeColorAlpha,
4
4
  createSharedStyle,
@@ -16,7 +16,7 @@ import {
16
16
  STATUS_BAR_OFFSET,
17
17
  } from './const';
18
18
  import { Device } from './helpers';
19
- import { resolveVariable } from './services';
19
+ import { isAndroid, isIos, resolveVariable } from './services';
20
20
 
21
21
  const window = Dimensions.get('window');
22
22
 
@@ -72,7 +72,7 @@ export function resolveFontFamily(
72
72
  fontWeight = 'normal',
73
73
  fontStyle = 'normal',
74
74
  ) {
75
- if (Platform.OS === 'ios') {
75
+ if (!isAndroid) {
76
76
  return fontName;
77
77
  }
78
78
 
@@ -103,22 +103,22 @@ export function resolveFontFamily(
103
103
  // being provided to fontWeight will cause the default system font to be used, so we conditionally
104
104
  // resolve it.
105
105
  export function resolveFontWeight(fontWeight) {
106
- if (Platform.OS === 'ios') {
107
- return fontWeight;
106
+ if (isAndroid) {
107
+ return 'normal';
108
108
  }
109
109
 
110
- return 'normal';
110
+ return fontWeight;
111
111
  }
112
112
 
113
113
  // Currently, resolveFontFamily will provide fontStyle styling, but any value other than 'normal'
114
114
  // being provided to fontStyle will cause the default system font to be used, so we conditionally
115
115
  // resolve it.
116
116
  export function resolveFontStyle(fontStyle) {
117
- if (Platform.OS === 'ios') {
118
- return fontStyle;
117
+ if (isAndroid) {
118
+ return 'normal';
119
119
  }
120
120
 
121
- return 'normal';
121
+ return fontStyle;
122
122
  }
123
123
 
124
124
  // This function is deprecated and replaced with calculateLineHeight.
@@ -1819,7 +1819,7 @@ export default () => {
1819
1819
  top: Device.select({
1820
1820
  iPhoneX: 6,
1821
1821
  iPhoneXR: 8,
1822
- default: Platform.OS === 'android' ? 0 : -4,
1822
+ default: isAndroid ? 0 : -4,
1823
1823
  }),
1824
1824
  left: 0,
1825
1825
  right: 0,
@@ -2427,7 +2427,7 @@ export default () => {
2427
2427
  number: {
2428
2428
  // Font should be monospace so that item content has same offset
2429
2429
  // Can not apply width to the Text for some reason
2430
- fontFamily: Platform.OS === 'ios' ? 'Menlo-Regular' : 'monospace',
2430
+ fontFamily: isIos ? 'Menlo-Regular' : 'monospace',
2431
2431
  fontSize: 12,
2432
2432
  },
2433
2433
  bullet: {},
@@ -2475,8 +2475,7 @@ export default () => {
2475
2475
  'shoutem.ui.Video': {
2476
2476
  container: {
2477
2477
  backgroundColor: resolveVariable('paperColor'),
2478
- flex: 1,
2479
- height: 240,
2478
+ height: responsiveHeight(240),
2480
2479
  },
2481
2480
  },
2482
2481
 
@@ -2714,13 +2713,7 @@ export default () => {
2714
2713
 
2715
2714
  'shoutem.ui.ActionSheet': {
2716
2715
  container: {
2717
- position: 'absolute',
2718
- top: 0,
2719
- bottom: 0,
2720
- left: 0,
2721
- right: 0,
2722
- flexDirection: 'column',
2723
- justifyContent: 'flex-end',
2716
+ backgroundColor: 'transparent',
2724
2717
  },
2725
2718
  contentContainer: {
2726
2719
  marginHorizontal: 8,
@@ -2744,10 +2737,11 @@ export default () => {
2744
2737
  text: {
2745
2738
  fontSize: 15,
2746
2739
  letterSpacing: 0.38,
2747
- color: '#000000',
2740
+ color: resolveVariable('primaryButtonText.color'),
2748
2741
  lineHeight: 24,
2749
2742
  },
2750
2743
  cancelText: {
2744
+ color: resolveVariable('errorText.color'),
2751
2745
  textAlign: 'center',
2752
2746
  fontWeight: resolveFontWeight('700'),
2753
2747
  },
@@ -0,0 +1,69 @@
1
+ .alert-container {
2
+ border-bottom: 1px solid rgba(0,0,0,0.1);
3
+ font-size: 15px;
4
+ }
5
+
6
+ .alert-popup {
7
+ border-radius: 18px;
8
+ padding-bottom: 0px;
9
+ overflow: hidden;
10
+ }
11
+
12
+ .alert-title {
13
+ color: black;
14
+ font-size: 20px;
15
+ margin-top: 10px;
16
+ margin-bottom: -10px;
17
+ }
18
+
19
+ .alert-actions {
20
+ border-top: 1px solid #DEDEDE;
21
+ margin: 20px 0px 0px 0px;
22
+ }
23
+
24
+ .alert-confirm {
25
+ color: cornflowerblue;
26
+ background-color: transparent;
27
+ margin: 0;
28
+ height: 100%;
29
+ border-radius: 0;
30
+ padding: 15px;
31
+ }
32
+
33
+ .alert-cancel {
34
+ color: cornflowerblue;
35
+ background-color: transparent;
36
+ margin: 0;
37
+ height: 100%;
38
+ border-radius: 0;
39
+ padding: 15px;
40
+ }
41
+
42
+ .alert-deny {
43
+ color: red;
44
+ background-color: transparent;
45
+ margin: 0;
46
+ height: 100%;
47
+ border-radius: 0;
48
+ padding: 15px;
49
+ }
50
+
51
+ .full-width {
52
+ width: 100%;
53
+ }
54
+
55
+ .half-width {
56
+ width: 50%;
57
+ }
58
+
59
+ .button-height {
60
+ height: 50px;
61
+ }
62
+
63
+ .border-right {
64
+ border-right: 1px solid #DEDEDE;
65
+ }
66
+
67
+ .border-bottom {
68
+ border-bottom: 1px solid #DEDEDE;
69
+ }