@shoutem/ui 4.6.2 → 4.6.6-rc.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.
@@ -13,15 +13,39 @@ import { View } from './View';
13
13
 
14
14
  const isIos = Platform.OS === 'ios';
15
15
 
16
+ export const DATEPICKER_MODES = {
17
+ DATE: 'date',
18
+ DATETIME: 'datetime',
19
+ TIME: 'time',
20
+ };
21
+
22
+ const DISPLAY_MODES = {
23
+ [DATEPICKER_MODES.DATE]: 'inline',
24
+ [DATEPICKER_MODES.DATETIME]: 'inline',
25
+ [DATEPICKER_MODES.TIME]: 'spinner',
26
+ };
27
+
28
+ function getDisplayMode(mode) {
29
+ if (!isIos || !Object.keys(DISPLAY_MODES).includes(mode)) {
30
+ return 'default';
31
+ }
32
+
33
+ return DISPLAY_MODES[mode];
34
+ }
35
+
16
36
  class DateTimePicker extends PureComponent {
17
37
  constructor(props) {
18
38
  super(props);
19
39
 
20
40
  autoBindReact(this);
21
41
 
42
+ const { mode, value } = props;
43
+
22
44
  this.state = {
45
+ display: getDisplayMode(mode),
23
46
  showPicker: false,
24
- value: props.value,
47
+ showTimePicker: false,
48
+ value,
25
49
  };
26
50
  }
27
51
 
@@ -42,10 +66,33 @@ class DateTimePicker extends PureComponent {
42
66
  return this.handleHidePicker();
43
67
  }
44
68
 
69
+ const { mode, onValueChanged } = this.props;
70
+
71
+ const showTimePicker = mode === DATEPICKER_MODES.DATETIME;
72
+
73
+ this.setState({ value, showPicker: false, showTimePicker });
74
+ return !showTimePicker && onValueChanged(value);
75
+ }
76
+
77
+ handleTimeValueChanged(event, selectedValue) {
78
+ if (event.type === 'dismissed') {
79
+ return this.handleHidePicker();
80
+ }
81
+
45
82
  const { onValueChanged } = this.props;
83
+ const { value } = this.state;
46
84
 
47
- this.setState({ showPicker: false });
48
- return onValueChanged(value);
85
+ // Time picker value will override preselected date
86
+ // Use current date from state and only set hours & minutes
87
+ const currentDate = new Date(value);
88
+ const selectedDate = new Date(selectedValue);
89
+ const hour = selectedDate.getHours();
90
+ const minutes = selectedDate.getMinutes();
91
+
92
+ currentDate.setHours(hour, minutes, 0);
93
+
94
+ this.setState({ value: currentDate, showTimePicker: false });
95
+ return onValueChanged(currentDate);
49
96
  }
50
97
 
51
98
  handleShowPicker() {
@@ -66,6 +113,7 @@ class DateTimePicker extends PureComponent {
66
113
 
67
114
  render() {
68
115
  const {
116
+ cancelButtonText,
69
117
  confirmButtonText,
70
118
  is24Hour,
71
119
  mode,
@@ -73,7 +121,7 @@ class DateTimePicker extends PureComponent {
73
121
  style,
74
122
  ...otherProps
75
123
  } = this.props;
76
- const { showPicker, value } = this.state;
124
+ const { display, showPicker, showTimePicker, value } = this.state;
77
125
 
78
126
  return (
79
127
  <View styleName="horizontal">
@@ -82,7 +130,7 @@ class DateTimePicker extends PureComponent {
82
130
  style={style.textContainer}
83
131
  onPress={this.handleShowPicker}
84
132
  >
85
- <Text> {textValue}</Text>
133
+ <Text>{textValue}</Text>
86
134
  </TouchableOpacity>
87
135
  <TouchableOpacity
88
136
  onPress={this.handleShowPicker}
@@ -91,7 +139,7 @@ class DateTimePicker extends PureComponent {
91
139
  >
92
140
  <Icon name="drop-down" style={style.icon} />
93
141
  </TouchableOpacity>
94
- {isIos && (
142
+ {isIos && showPicker && (
95
143
  <Modal
96
144
  backdropOpacity={0.5}
97
145
  isVisible={showPicker}
@@ -103,7 +151,7 @@ class DateTimePicker extends PureComponent {
103
151
  >
104
152
  <View styleName="md-gutter" style={style.modalContainer}>
105
153
  <RNCDateTimePicker
106
- display="spinner"
154
+ display={display}
107
155
  is24Hour={is24Hour}
108
156
  mode={mode}
109
157
  onChange={this.handleValueChanged}
@@ -114,8 +162,14 @@ class DateTimePicker extends PureComponent {
114
162
  />
115
163
  <View
116
164
  style={style.modalButtonContainer}
117
- styleName="md-gutter-top"
165
+ styleName="horizontal md-gutter-top"
118
166
  >
167
+ <Button
168
+ style={style.modalButton}
169
+ onPress={this.handleHidePicker}
170
+ >
171
+ <Text>{cancelButtonText}</Text>
172
+ </Button>
119
173
  <Button
120
174
  style={style.modalButton}
121
175
  onPress={this.handleConfirmPress}
@@ -128,7 +182,7 @@ class DateTimePicker extends PureComponent {
128
182
  )}
129
183
  {!isIos && showPicker && (
130
184
  <RNCDateTimePicker
131
- display="default"
185
+ display={display}
132
186
  is24Hour={is24Hour}
133
187
  mode={mode}
134
188
  onChange={this.handleValueChanged}
@@ -136,21 +190,33 @@ class DateTimePicker extends PureComponent {
136
190
  {...otherProps}
137
191
  />
138
192
  )}
193
+ {!isIos && !showPicker && showTimePicker && (
194
+ <RNCDateTimePicker
195
+ display={display}
196
+ is24Hour={is24Hour}
197
+ mode="time"
198
+ onChange={this.handleTimeValueChanged}
199
+ value={value}
200
+ {...otherProps}
201
+ />
202
+ )}
139
203
  </View>
140
204
  );
141
205
  }
142
206
  }
143
207
 
144
208
  DateTimePicker.propTypes = {
209
+ cancelButtonText: PropTypes.string,
145
210
  confirmButtonText: PropTypes.string,
146
211
  is24Hour: PropTypes.bool,
147
- mode: PropTypes.string,
212
+ mode: PropTypes.oneOf(Object.values(DATEPICKER_MODES)),
148
213
  onValueChanged: PropTypes.func,
149
214
  textValue: PropTypes.string,
150
215
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]),
151
216
  };
152
217
 
153
218
  DateTimePicker.defaultProps = {
219
+ cancelButtonText: 'Cancel',
154
220
  confirmButtonText: 'Confirm',
155
221
  is24Hour: false,
156
222
  mode: 'datetime',
@@ -77,6 +77,7 @@ class ListView extends PureComponent {
77
77
 
78
78
  this.state = {
79
79
  status: props.loading ? Status.LOADING : Status.IDLE,
80
+ isScrolling: false,
80
81
  };
81
82
  }
82
83
 
@@ -196,17 +197,21 @@ class ListView extends PureComponent {
196
197
  // reference
197
198
  mappedProps.ref = this.handleListViewRef;
198
199
 
200
+ mappedProps.onMomentumScrollBegin = this.setStartedScrolling;
201
+
202
+ mappedProps.onMomentumScrollEnd = this.setEndedScrolling;
203
+
199
204
  return mappedProps;
200
205
  }
201
206
 
202
207
  // eslint-disable-next-line consistent-return
203
208
  createOnLoadMore() {
204
209
  const { onLoadMore, data } = this.props;
205
- const { status } = this.state;
210
+ const { isScrolling, status } = this.state;
206
211
  if (onLoadMore) {
207
212
  return _.throttle(
208
213
  () => {
209
- if (!_.isEmpty(data) && status === Status.IDLE) {
214
+ if (!_.isEmpty(data) && isScrolling && status === Status.IDLE) {
210
215
  onLoadMore();
211
216
  }
212
217
  },
@@ -216,6 +221,14 @@ class ListView extends PureComponent {
216
221
  }
217
222
  }
218
223
 
224
+ setStartedScrolling() {
225
+ this.setState({ isScrolling: true });
226
+ }
227
+
228
+ setEndedScrolling() {
229
+ this.setState({ isScrolling: true });
230
+ }
231
+
219
232
  autoHideHeader({
220
233
  nativeEvent: {
221
234
  layout: { height },
@@ -25,6 +25,34 @@ import getEmptyObjectKeys from '../services/getEmptyObjectKeys';
25
25
  import isValidVideoFormat from '../services/isValidVideoFormat';
26
26
  import Image from './Image';
27
27
 
28
+ function resolveMaxWidth(style) {
29
+ // parentContainerPadding - padding between screen & SimpleHtml. Parent container
30
+ // can have it's own padding. If it does, we have to include it in calculation,
31
+ // otherwise maxWidth will be more than max & images will go over the edge
32
+ const parentContainerPadding = style.outerPadding || 0;
33
+ const paddingValue =
34
+ style.container.paddingLeft ||
35
+ 0 + style.container.paddingRight ||
36
+ 0 + parentContainerPadding;
37
+
38
+ return Dimensions.get('window').width - paddingValue;
39
+ }
40
+
41
+ function resolveDimensions(objectToResize, style) {
42
+ const { width, height } = objectToResize;
43
+
44
+ if (!width || !height) {
45
+ return { width, height };
46
+ }
47
+
48
+ const maxWidth = resolveMaxWidth(style);
49
+ const objectToResizeRatio = height / width;
50
+ const resolvedWidth = width > maxWidth ? maxWidth : width;
51
+ const resolvedHeight = Math.round(resolvedWidth * objectToResizeRatio);
52
+
53
+ return { width: resolvedWidth, height: resolvedHeight };
54
+ }
55
+
28
56
  class SimpleHtml extends PureComponent {
29
57
  static propTypes = {
30
58
  body: PropTypes.string,
@@ -33,6 +61,7 @@ class SimpleHtml extends PureComponent {
33
61
  customTagStyles: PropTypes.object,
34
62
  customHandleLinkPress: PropTypes.func,
35
63
  unsupportedVideoFormatMessage: PropTypes.string,
64
+ customAlterNode: PropTypes.func,
36
65
  };
37
66
 
38
67
  constructor(props) {
@@ -56,10 +85,22 @@ class SimpleHtml extends PureComponent {
56
85
  * video iframe tags in when video format is unsupported
57
86
  */
58
87
  alterNode(node) {
59
- const { style } = this.props;
88
+ const { customAlterNode, style } = this.props;
60
89
 
61
90
  const styleAttrib = _.get(node, 'attribs.style', '').trim();
62
91
 
92
+ if (customAlterNode && _.isFunction(customAlterNode)) {
93
+ const resolvedNode = customAlterNode(
94
+ node,
95
+ cssObjectToString,
96
+ cssStringToObject,
97
+ );
98
+
99
+ if (resolvedNode) {
100
+ return resolvedNode;
101
+ }
102
+ }
103
+
63
104
  if (node.name === 'table') {
64
105
  return tableAlterNode(node);
65
106
  }
@@ -81,23 +122,15 @@ class SimpleHtml extends PureComponent {
81
122
  }
82
123
  }
83
124
 
84
- const nodeWidth = _.get(node, 'attribs.width', false);
125
+ const nodeWidth = node.attribs?.width;
85
126
 
86
127
  if (!styleAttrib && !nodeWidth) {
87
128
  return false;
88
129
  }
89
130
 
90
- // parentContainerPadding - padding between screen & SimpleHtml. Parent container
91
- // can have it's own padding. If it does, we have to include it in calculation,
92
- // otherwise maxWidth will be more than max & images will go over the edge
93
- const parentContainerPadding = style.outerPadding || 0;
94
- const paddingValue =
95
- _.get(style, 'container.paddingLeft') * 2 + parentContainerPadding;
96
- const maxWidth = Dimensions.get('window').width - paddingValue;
97
- const nodeHeight = _.get(node, 'attribus.height');
98
- const nodeRatio = nodeWidth / nodeHeight;
99
- const resolvedWidth = nodeWidth > maxWidth ? maxWidth : nodeWidth;
100
- const resolvedHeight = Math.round(resolvedWidth * nodeRatio);
131
+ const nodeHeight = node.attribus?.height;
132
+ const nodeDimensions = { width: nodeWidth, height: nodeHeight };
133
+ const { width, height } = resolveDimensions(nodeDimensions, style);
101
134
 
102
135
  const nodeStyle = cssStringToObject(styleAttrib);
103
136
  const invalidKeys = getEmptyObjectKeys(nodeStyle);
@@ -105,8 +138,8 @@ class SimpleHtml extends PureComponent {
105
138
  if (invalidKeys.length || nodeWidth) {
106
139
  const styleFiltered = _.omit(style, invalidKeys);
107
140
  node.attribs.style = cssObjectToString(styleFiltered);
108
- node.attribs.width = resolvedWidth;
109
- node.attribs.height = resolvedHeight;
141
+ node.attribs.width = width;
142
+ node.attribs.height = height;
110
143
 
111
144
  return node;
112
145
  }
@@ -162,7 +195,7 @@ class SimpleHtml extends PureComponent {
162
195
  * Currently only supports rendering image attachments.
163
196
  */
164
197
  renderAttachments({ id, type }) {
165
- const { attachments } = this.props;
198
+ const { attachments, style } = this.props;
166
199
 
167
200
  if (!attachments) {
168
201
  return null;
@@ -173,9 +206,11 @@ class SimpleHtml extends PureComponent {
173
206
 
174
207
  if (image && image.src) {
175
208
  const source = { uri: image.src };
176
- const style = { height: image.height, alignSelf: 'center' };
209
+ const imageSize = { width: image.width, height: image.height };
210
+ const { height, width } = resolveDimensions(imageSize, style);
211
+ const resolvedStyle = { alignSelf: 'center', height, width };
177
212
 
178
- return <Image source={source} key={id} style={style} />;
213
+ return <Image source={source} key={id} style={resolvedStyle} />;
179
214
  }
180
215
  }
181
216
 
@@ -225,13 +260,7 @@ class SimpleHtml extends PureComponent {
225
260
  render() {
226
261
  const { style, body, customTagStyles, ...otherProps } = this.props;
227
262
 
228
- // parentContainerPadding - padding between screen & SimpleHtml. Parent container
229
- // can have it's own padding. If it does, we have to include it in calculation,
230
- // otherwise maxWidth will be more than max & images will go over the edge
231
- const parentContainerPadding = style.outerPadding || 0;
232
- const paddingValue =
233
- _.get(style, 'container.paddingLeft') * 2 + parentContainerPadding;
234
- const maxWidth = Dimensions.get('window').width - paddingValue;
263
+ const maxWidth = resolveMaxWidth(style);
235
264
 
236
265
  const tagStyles = {
237
266
  ...style.tags,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shoutem/ui",
3
- "version": "4.6.2",
3
+ "version": "4.6.6-rc.0",
4
4
  "description": "Styleable set of components for React Native applications",
5
5
  "scripts": {
6
6
  "lint": "eslint .",
@@ -35,7 +35,7 @@
35
35
  "react-native-transformable-image": "shoutem/react-native-transformable-image#v0.0.20",
36
36
  "react-native-vector-icons": "~6.6.0",
37
37
  "react-native-webview": "~11.0.0",
38
- "react-native-youtube-iframe": "~2.0.1",
38
+ "react-native-youtube-iframe": "~2.1.2",
39
39
  "stream": "0.0.2",
40
40
  "tinycolor2": "1.4.1"
41
41
  },
package/theme.js CHANGED
@@ -900,6 +900,8 @@ export default (variables = defaultThemeVariables) => ({
900
900
  },
901
901
 
902
902
  'shoutem.ui.Screen': {
903
+ [INCLUDE]: ['commonVariants', 'guttersPadding'],
904
+
903
905
  '.full-screen': {
904
906
  marginTop: -(NAVIGATION_BAR_HEIGHT + StyleSheet.hairlineWidth),
905
907
  },
@@ -2041,10 +2043,9 @@ export default (variables = defaultThemeVariables) => ({
2041
2043
  minHeight: 40,
2042
2044
  maxHeight: 200,
2043
2045
  justifyContent: 'center',
2044
- backgroundColor: inverseColorBrightnessForAmount(
2045
- variables.paperColor,
2046
- 5,
2047
- ),
2046
+ backgroundColor:
2047
+ variables.categoryDropdownBackgroundColor ||
2048
+ inverseColorBrightnessForAmount(variables.paperColor, 5),
2048
2049
  width: window.width,
2049
2050
  marginTop: 0,
2050
2051
  borderBottomWidth: StyleSheet.hairlineWidth,
@@ -2052,7 +2053,7 @@ export default (variables = defaultThemeVariables) => ({
2052
2053
  },
2053
2054
  selectedOption: {
2054
2055
  'shoutem.ui.Icon': {
2055
- color: variables.text.color,
2056
+ color: variables.categoryDropdownTextColor || variables.text.color,
2056
2057
  },
2057
2058
  'shoutem.ui.Text': {
2058
2059
  ...variables.navBarText,
@@ -2061,7 +2062,7 @@ export default (variables = defaultThemeVariables) => ({
2061
2062
  'normal',
2062
2063
  variables.navBarText.fontStyle,
2063
2064
  ),
2064
- color: variables.text.color,
2065
+ color: variables.categoryDropdownTextColor || variables.text.color,
2065
2066
  fontWeight: resolveFontWeight('normal'),
2066
2067
  fontStyle: resolveFontStyle(variables.navBarText.fontStyle),
2067
2068
  textAlign: 'center',