@shoutem/ui 6.2.0 → 6.4.0-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.
- package/components/Icon/assets/index.js +0 -1
- package/components/Switch.js +17 -72
- package/helpers/device-selector.js +47 -2
- package/html/components/AttachmentRenderer.js +42 -0
- package/html/components/IframeRenderer.js +60 -0
- package/html/components/SimpleHtml.js +57 -247
- package/html/services/Dimensions.js +29 -0
- package/package.json +5 -4
- package/theme.js +17 -60
|
@@ -171,7 +171,6 @@ export const defaultConfig = [
|
|
|
171
171
|
{ name: 'forward', icon: forward },
|
|
172
172
|
{ name: 'gallery', icon: gallery },
|
|
173
173
|
{ name: 'friends', icon: friends },
|
|
174
|
-
{ name: 'gallery', icon: gallery },
|
|
175
174
|
{ name: 'garbage-can', icon: garbageCan },
|
|
176
175
|
{ name: 'gift', icon: gift },
|
|
177
176
|
{ name: 'github', icon: github },
|
package/components/Switch.js
CHANGED
|
@@ -1,87 +1,32 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import {
|
|
3
|
-
import autoBindReact from 'auto-bind/react';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Switch as RNSwitch } from 'react-native';
|
|
4
3
|
import PropTypes from 'prop-types';
|
|
5
|
-
import { connectAnimation, TimingDriver } from '@shoutem/animation';
|
|
6
4
|
import { connectStyle } from '@shoutem/theme';
|
|
7
5
|
import { View } from './View';
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
componentDidUpdate(prevProps) {
|
|
21
|
-
const { value: currentValue } = this.props;
|
|
22
|
-
|
|
23
|
-
if (prevProps.value !== currentValue) {
|
|
24
|
-
this.setValue(currentValue);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
onSwitchPressed() {
|
|
29
|
-
const { value, onValueChange } = this.props;
|
|
30
|
-
|
|
31
|
-
onValueChange(!value);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
setValue(value) {
|
|
35
|
-
this.timingDriver.toValue(value ? 1 : 0);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
render() {
|
|
39
|
-
const { style } = this.props;
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<TouchableWithoutFeedback
|
|
43
|
-
onPress={this.onSwitchPressed}
|
|
44
|
-
styleName="clear"
|
|
45
|
-
>
|
|
46
|
-
<View>
|
|
47
|
-
<View
|
|
48
|
-
animationName="mute"
|
|
49
|
-
driver={this.timingDriver}
|
|
50
|
-
style={style.container}
|
|
51
|
-
>
|
|
52
|
-
<View
|
|
53
|
-
animationName="turn"
|
|
54
|
-
animationOptions={{ containerWidth: style.container.width }}
|
|
55
|
-
driver={this.timingDriver}
|
|
56
|
-
style={style.thumb}
|
|
57
|
-
/>
|
|
58
|
-
</View>
|
|
59
|
-
</View>
|
|
60
|
-
</TouchableWithoutFeedback>
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
7
|
+
const Switch = ({ value, onValueChange, style }) => (
|
|
8
|
+
<View style={style.container}>
|
|
9
|
+
<RNSwitch
|
|
10
|
+
trackColor={style.track}
|
|
11
|
+
thumbColor={style.thumb}
|
|
12
|
+
ios_backgroundColor={style.track?.false}
|
|
13
|
+
onValueChange={onValueChange}
|
|
14
|
+
value={value}
|
|
15
|
+
/>
|
|
16
|
+
</View>
|
|
17
|
+
);
|
|
64
18
|
|
|
65
19
|
Switch.propTypes = {
|
|
66
|
-
|
|
67
|
-
style: PropTypes.
|
|
68
|
-
// Container style
|
|
69
|
-
container: PropTypes.object,
|
|
70
|
-
// Thumb style
|
|
71
|
-
thumb: PropTypes.object,
|
|
72
|
-
}).isRequired,
|
|
73
|
-
// True when switch is on, false otherwise
|
|
20
|
+
onValueChange: PropTypes.func.isRequired,
|
|
21
|
+
style: PropTypes.object,
|
|
74
22
|
value: PropTypes.bool,
|
|
75
|
-
// Called when switch is toggled on and off
|
|
76
|
-
onValueChange: PropTypes.func,
|
|
77
23
|
};
|
|
78
24
|
|
|
79
25
|
Switch.defaultProps = {
|
|
80
26
|
value: false,
|
|
81
|
-
|
|
27
|
+
style: {},
|
|
82
28
|
};
|
|
83
29
|
|
|
84
|
-
const
|
|
85
|
-
const StyledSwitch = connectStyle('shoutem.ui.Switch')(AnimatedSwitch);
|
|
30
|
+
const StyledSwitch = connectStyle('shoutem.ui.Switch')(Switch);
|
|
86
31
|
|
|
87
32
|
export { StyledSwitch as Switch };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Dimensions, Platform, StatusBar } from 'react-native';
|
|
2
|
-
import
|
|
2
|
+
import DeviceInfo from 'react-native-device-info';
|
|
3
3
|
import _ from 'lodash';
|
|
4
4
|
|
|
5
5
|
const { OS, isPad, isTVOS } = Platform;
|
|
@@ -37,7 +37,7 @@ const isIphoneXR =
|
|
|
37
37
|
!isTVOS &&
|
|
38
38
|
(xrDimensionsMatch || twelveDimensionsMatch || twelveMaxDimensionsMatch);
|
|
39
39
|
|
|
40
|
-
const isNotchedAndroid = OS === 'android' && hasNotch();
|
|
40
|
+
const isNotchedAndroid = OS === 'android' && DeviceInfo.hasNotch();
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Receives settings for different devices
|
|
@@ -71,6 +71,51 @@ export const NAVIGATION_BAR_HEIGHT = select({
|
|
|
71
71
|
default: NAVIGATION_HEADER_HEIGHT,
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
+
export const getHomeIndicatorPadding = () => {
|
|
75
|
+
if (Platform.OS === 'android') {
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Apple identifier
|
|
80
|
+
const deviceId = DeviceInfo.getDeviceId();
|
|
81
|
+
|
|
82
|
+
// Extract the numbers from the deviceId string using a regular expression
|
|
83
|
+
const versions = deviceId.match(/(\d+),(\d+)/);
|
|
84
|
+
|
|
85
|
+
// Only checks if regex found X,Y, it does not resolve any of device ID-padding logic.
|
|
86
|
+
// If version is present, e.g. in iPhone 9,1, it'll always return array of length 3.
|
|
87
|
+
if (!versions && versions.length < 3) {
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Extracted numbers as strings
|
|
92
|
+
const majorVersion = versions[1];
|
|
93
|
+
const minorVersion = versions[2];
|
|
94
|
+
|
|
95
|
+
const numericVersion = parseFloat(`${majorVersion}.${minorVersion}`);
|
|
96
|
+
|
|
97
|
+
const excludedIphones = {
|
|
98
|
+
iPhone8: 10.4,
|
|
99
|
+
iPhone8plus: 10.5,
|
|
100
|
+
iPhoneSeGen2: 12.8,
|
|
101
|
+
iPhoneSeGen3: 14.6,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Home indicator is present since iPhone X series with id of iPhone10,3.
|
|
105
|
+
// There are few newer models that do not have home indicator.
|
|
106
|
+
// They're included in excludedIphones object above.
|
|
107
|
+
if (
|
|
108
|
+
numericVersion >= 10.3 &&
|
|
109
|
+
!Object.values(excludedIphones).includes(numericVersion)
|
|
110
|
+
) {
|
|
111
|
+
// Devices with home indicator
|
|
112
|
+
return 34;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Devices without home indicator
|
|
116
|
+
return 0;
|
|
117
|
+
};
|
|
118
|
+
|
|
74
119
|
export const Device = {
|
|
75
120
|
isIphoneX,
|
|
76
121
|
isIphoneXR,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import { resolveDimensions } from '../services/Dimensions';
|
|
5
|
+
import Image from './Image';
|
|
6
|
+
|
|
7
|
+
const AttachmentRenderer = ({ id, type, style, attachments }) => {
|
|
8
|
+
if (!attachments) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (type === 'image') {
|
|
13
|
+
const image = _.find(attachments, { id });
|
|
14
|
+
|
|
15
|
+
if (image && image.src) {
|
|
16
|
+
const source = { uri: image.src };
|
|
17
|
+
const imageSize = { width: image.width, height: image.height };
|
|
18
|
+
const { height, width } = resolveDimensions(imageSize, style);
|
|
19
|
+
const resolvedStyle = { alignSelf: 'center', height, width };
|
|
20
|
+
|
|
21
|
+
return <Image source={source} key={id} style={resolvedStyle} />;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
AttachmentRenderer.propTypes = {
|
|
29
|
+
attachments: PropTypes.oneOf([PropTypes.array, PropTypes.object]),
|
|
30
|
+
id: PropTypes.any,
|
|
31
|
+
style: PropTypes.any,
|
|
32
|
+
type: PropTypes.any,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
AttachmentRenderer.defaultProps = {
|
|
36
|
+
id: undefined,
|
|
37
|
+
type: undefined,
|
|
38
|
+
attachments: undefined,
|
|
39
|
+
style: undefined,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default AttachmentRenderer;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import YoutubePlayer from 'react-native-youtube-iframe';
|
|
3
|
+
import iframe from '@native-html/iframe-plugin';
|
|
4
|
+
import PropTypes from 'prop-types';
|
|
5
|
+
import { Text } from '../../components/Text';
|
|
6
|
+
import { View } from '../../components/View';
|
|
7
|
+
import isValidVideoFormat from '../services/isValidVideoFormat';
|
|
8
|
+
|
|
9
|
+
const IframeRenderer = props => {
|
|
10
|
+
const { tnode, shoutemStyle, unsupportedVideoFormatMessage } = props;
|
|
11
|
+
const url = tnode?.domNode?.attribs?.src;
|
|
12
|
+
|
|
13
|
+
if (url && !isValidVideoFormat(url)) {
|
|
14
|
+
const message =
|
|
15
|
+
unsupportedVideoFormatMessage || 'Unsupported video format.';
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<View
|
|
19
|
+
style={shoutemStyle.fallback}
|
|
20
|
+
styleName="vertical h-center v-center"
|
|
21
|
+
>
|
|
22
|
+
<Text>{message}</Text>
|
|
23
|
+
</View>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (url && (url.includes('youtube') || url.includes('youtu.be'))) {
|
|
28
|
+
const youtubeIdRegEx = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
|
|
29
|
+
const regExMatch = url.match(youtubeIdRegEx);
|
|
30
|
+
const youtubeId =
|
|
31
|
+
regExMatch && regExMatch[7].length === 11 ? regExMatch[7] : false;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<YoutubePlayer
|
|
35
|
+
webViewProps={{
|
|
36
|
+
renderToHardwareTextureAndroid: true,
|
|
37
|
+
}}
|
|
38
|
+
height={shoutemStyle.video.height}
|
|
39
|
+
videoId={youtubeId}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return iframe(props);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
IframeRenderer.propTypes = {
|
|
48
|
+
tnode: PropTypes.shape({
|
|
49
|
+
children: PropTypes.any,
|
|
50
|
+
}).isRequired,
|
|
51
|
+
shoutemStyle: PropTypes.any,
|
|
52
|
+
unsupportedVideoFormatMessage: PropTypes.string,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
IframeRenderer.defaultProps = {
|
|
56
|
+
shoutemStyle: undefined,
|
|
57
|
+
unsupportedVideoFormatMessage: undefined,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default IframeRenderer;
|
|
@@ -1,57 +1,22 @@
|
|
|
1
1
|
import React, { PureComponent } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import Html from 'react-native-render-html';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
cssStringToObject,
|
|
8
|
-
} from 'react-native-render-html/src/HTMLStyles';
|
|
9
|
-
import {
|
|
10
|
-
alterNode as tableAlterNode,
|
|
2
|
+
import { Linking } from 'react-native';
|
|
3
|
+
import Html, { defaultSystemFonts } from 'react-native-render-html';
|
|
4
|
+
import WebView from 'react-native-webview';
|
|
5
|
+
import { iframeModel } from '@native-html/iframe-plugin';
|
|
6
|
+
import table, {
|
|
11
7
|
cssRulesFromSpecs,
|
|
12
8
|
defaultTableStylesSpecs,
|
|
13
9
|
IGNORED_TAGS,
|
|
14
|
-
|
|
15
|
-
} from '
|
|
16
|
-
import WebView from 'react-native-webview';
|
|
17
|
-
import YoutubePlayer from 'react-native-youtube-iframe';
|
|
10
|
+
tableModel,
|
|
11
|
+
} from '@native-html/table-plugin';
|
|
18
12
|
import autoBindReact from 'auto-bind/react';
|
|
19
13
|
import _ from 'lodash';
|
|
20
14
|
import PropTypes from 'prop-types';
|
|
21
15
|
import { connectStyle } from '@shoutem/theme';
|
|
22
|
-
import { Text } from '../../components/Text';
|
|
23
16
|
import { View } from '../../components/View';
|
|
24
|
-
import
|
|
25
|
-
import
|
|
26
|
-
import
|
|
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
|
-
}
|
|
17
|
+
import { resolveMaxWidth } from '../services/Dimensions';
|
|
18
|
+
import AttachmentRenderer from './AttachmentRenderer';
|
|
19
|
+
import IframeRenderer from './IframeRenderer';
|
|
55
20
|
|
|
56
21
|
class SimpleHtml extends PureComponent {
|
|
57
22
|
constructor(props) {
|
|
@@ -60,7 +25,7 @@ class SimpleHtml extends PureComponent {
|
|
|
60
25
|
autoBindReact(this);
|
|
61
26
|
}
|
|
62
27
|
|
|
63
|
-
onLinkPress(
|
|
28
|
+
onLinkPress(_evt, href) {
|
|
64
29
|
const { customHandleLinkPress } = this.props;
|
|
65
30
|
|
|
66
31
|
return customHandleLinkPress
|
|
@@ -68,190 +33,15 @@ class SimpleHtml extends PureComponent {
|
|
|
68
33
|
: Linking.openURL(href);
|
|
69
34
|
}
|
|
70
35
|
|
|
71
|
-
/**
|
|
72
|
-
* Removes empty (therefore invalid) style attribute properties
|
|
73
|
-
* Scales down objects with specified width and height if too large
|
|
74
|
-
* Removes padding and height from suneditor for 'figure' tag that it nests
|
|
75
|
-
* video iframe tags in when video format is unsupported
|
|
76
|
-
*/
|
|
77
|
-
alterNode(node) {
|
|
78
|
-
const { customAlterNode, style } = this.props;
|
|
79
|
-
|
|
80
|
-
const styleAttrib = _.get(node, 'attribs.style', '').trim();
|
|
81
|
-
|
|
82
|
-
if (customAlterNode && _.isFunction(customAlterNode)) {
|
|
83
|
-
const resolvedNode = customAlterNode(
|
|
84
|
-
node,
|
|
85
|
-
cssObjectToString,
|
|
86
|
-
cssStringToObject,
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
if (resolvedNode) {
|
|
90
|
-
return resolvedNode;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (node.name === 'table') {
|
|
95
|
-
return tableAlterNode(node);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (node.name === 'figure') {
|
|
99
|
-
const firstChild = _.head(node.children);
|
|
100
|
-
|
|
101
|
-
if (firstChild.name === 'iframe') {
|
|
102
|
-
const nodeStyle = cssStringToObject(styleAttrib);
|
|
103
|
-
const source = _.get(firstChild, 'attribs.src', '');
|
|
104
|
-
|
|
105
|
-
const resolvedNodeStyle = isValidVideoFormat(source)
|
|
106
|
-
? _.omit(nodeStyle, ['height', 'padding-bottom'])
|
|
107
|
-
: nodeStyle;
|
|
108
|
-
|
|
109
|
-
node.attribs.style = cssObjectToString(resolvedNodeStyle);
|
|
110
|
-
|
|
111
|
-
return node;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const nodeWidth = node.attribs?.width;
|
|
116
|
-
|
|
117
|
-
if (!styleAttrib && !nodeWidth) {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const nodeHeight = node.attribus?.height;
|
|
122
|
-
const nodeDimensions = { width: nodeWidth, height: nodeHeight };
|
|
123
|
-
const { width, height } = resolveDimensions(nodeDimensions, style);
|
|
124
|
-
|
|
125
|
-
const nodeStyle = cssStringToObject(styleAttrib);
|
|
126
|
-
const invalidKeys = getEmptyObjectKeys(nodeStyle);
|
|
127
|
-
|
|
128
|
-
if (invalidKeys.length || nodeWidth) {
|
|
129
|
-
const styleFiltered = _.omit(style, invalidKeys);
|
|
130
|
-
node.attribs.style = cssObjectToString(styleFiltered);
|
|
131
|
-
node.attribs.width = width;
|
|
132
|
-
node.attribs.height = height;
|
|
133
|
-
|
|
134
|
-
return node;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Removes child break node in case when it's positioned at the end
|
|
142
|
-
* of parent div node and div has content in it.
|
|
143
|
-
* Example:
|
|
144
|
-
* <div>first line<br></div>
|
|
145
|
-
* <div>second line<br></div>
|
|
146
|
-
*
|
|
147
|
-
* If br is left as is, <Html> will show empty line under div, not just
|
|
148
|
-
* break into new line
|
|
149
|
-
*/
|
|
150
|
-
alterChildren(node) {
|
|
151
|
-
const { children, name } = node;
|
|
152
|
-
|
|
153
|
-
if ((name === 'div' || name === 'p') && children && children.length > 1) {
|
|
154
|
-
const brNodes = _.filter(children, { name: 'br' });
|
|
155
|
-
const lastBrNode = _.last(brNodes);
|
|
156
|
-
|
|
157
|
-
if (lastBrNode && lastBrNode.next === null) {
|
|
158
|
-
const lastBrNodeIndex = _.indexOf(children, lastBrNode);
|
|
159
|
-
return children.splice(0, lastBrNodeIndex);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return children;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
renderUnorderedListPrefix() {
|
|
167
|
-
const { style } = this.props;
|
|
168
|
-
|
|
169
|
-
return <Text style={style.prefix}>• </Text>;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
renderOrderedListPrefix(
|
|
173
|
-
htmlAttribs,
|
|
174
|
-
children,
|
|
175
|
-
convertedCSSStyles,
|
|
176
|
-
passProps,
|
|
177
|
-
) {
|
|
178
|
-
const { style } = this.props;
|
|
179
|
-
|
|
180
|
-
return <Text style={style.prefix}>{passProps.index + 1}. </Text>;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Custom rendered method for handling <attachment> tags
|
|
185
|
-
* Currently only supports rendering image attachments.
|
|
186
|
-
*/
|
|
187
|
-
renderAttachments({ id, type }) {
|
|
188
|
-
const { attachments, style } = this.props;
|
|
189
|
-
|
|
190
|
-
if (!attachments) {
|
|
191
|
-
return null;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (type === 'image') {
|
|
195
|
-
const image = _.find(attachments, { id });
|
|
196
|
-
|
|
197
|
-
if (image && image.src) {
|
|
198
|
-
const source = { uri: image.src };
|
|
199
|
-
const imageSize = { width: image.width, height: image.height };
|
|
200
|
-
const { height, width } = resolveDimensions(imageSize, style);
|
|
201
|
-
const resolvedStyle = { alignSelf: 'center', height, width };
|
|
202
|
-
|
|
203
|
-
return <Image source={source} key={id} style={resolvedStyle} />;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return null;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
renderIframe(htmlAttribs, children, convertedCSSStyles, passProps) {
|
|
211
|
-
const { style } = this.props;
|
|
212
|
-
|
|
213
|
-
const url = htmlAttribs?.src;
|
|
214
|
-
|
|
215
|
-
if (url && !isValidVideoFormat(url)) {
|
|
216
|
-
const { unsupportedVideoFormatMessage } = this.props;
|
|
217
|
-
|
|
218
|
-
const message =
|
|
219
|
-
unsupportedVideoFormatMessage || 'Unsupported video format.';
|
|
220
|
-
|
|
221
|
-
return (
|
|
222
|
-
<View
|
|
223
|
-
style={style.fallback}
|
|
224
|
-
styleName="vertical h-center v-center"
|
|
225
|
-
key={passProps.key}
|
|
226
|
-
>
|
|
227
|
-
<Text>{message}</Text>
|
|
228
|
-
</View>
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (url && (url.includes('youtube') || url.includes('youtu.be'))) {
|
|
233
|
-
const youtubeIdRegEx = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
|
|
234
|
-
const regExMatch = url.match(youtubeIdRegEx);
|
|
235
|
-
const youtubeId =
|
|
236
|
-
regExMatch && regExMatch[7].length === 11 ? regExMatch[7] : false;
|
|
237
|
-
|
|
238
|
-
return (
|
|
239
|
-
<YoutubePlayer
|
|
240
|
-
webViewProps={{
|
|
241
|
-
renderToHardwareTextureAndroid: true,
|
|
242
|
-
}}
|
|
243
|
-
height={style.video.height}
|
|
244
|
-
key={passProps.key}
|
|
245
|
-
videoId={youtubeId}
|
|
246
|
-
/>
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return iframe(htmlAttribs, children, convertedCSSStyles, passProps);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
36
|
render() {
|
|
254
|
-
const {
|
|
37
|
+
const {
|
|
38
|
+
style,
|
|
39
|
+
body,
|
|
40
|
+
customTagStyles,
|
|
41
|
+
unsupportedVideoFormatMessage,
|
|
42
|
+
attachments,
|
|
43
|
+
...otherProps
|
|
44
|
+
} = this.props;
|
|
255
45
|
|
|
256
46
|
const maxWidth = resolveMaxWidth(style);
|
|
257
47
|
|
|
@@ -260,11 +50,6 @@ class SimpleHtml extends PureComponent {
|
|
|
260
50
|
...customTagStyles,
|
|
261
51
|
};
|
|
262
52
|
|
|
263
|
-
const listPrefixRenderers = {
|
|
264
|
-
ul: this.renderUnorderedListPrefix,
|
|
265
|
-
ol: this.renderOrderedListPrefix,
|
|
266
|
-
};
|
|
267
|
-
|
|
268
53
|
const tableStyle = _.get(style, 'table', {});
|
|
269
54
|
const tableCssStyle = _.get(style, 'tableCss', '');
|
|
270
55
|
const cssStyle = cssRulesFromSpecs({
|
|
@@ -274,23 +59,48 @@ class SimpleHtml extends PureComponent {
|
|
|
274
59
|
const cssRules = `${cssStyle}${tableCssStyle}`;
|
|
275
60
|
|
|
276
61
|
const customRenderers = {
|
|
277
|
-
iframe:
|
|
278
|
-
|
|
279
|
-
|
|
62
|
+
iframe: props => (
|
|
63
|
+
<IframeRenderer
|
|
64
|
+
{...props}
|
|
65
|
+
shoutemStyle={style}
|
|
66
|
+
unsupportedVideoFormatMessage={unsupportedVideoFormatMessage}
|
|
67
|
+
/>
|
|
68
|
+
),
|
|
69
|
+
table,
|
|
70
|
+
attachment: props => (
|
|
71
|
+
<AttachmentRenderer
|
|
72
|
+
{...props}
|
|
73
|
+
attachments={attachments}
|
|
74
|
+
style={style}
|
|
75
|
+
/>
|
|
76
|
+
),
|
|
280
77
|
};
|
|
281
78
|
|
|
282
79
|
const htmlProps = {
|
|
283
|
-
html: body,
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
tagsStyles: tagStyles,
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
alterNode: this.alterNode,
|
|
291
|
-
alterChildren: this.alterChildren,
|
|
292
|
-
listsPrefixesRenderers: listPrefixRenderers,
|
|
80
|
+
source: { html: body },
|
|
81
|
+
computeEmbeddedMaxWidth: () => maxWidth,
|
|
82
|
+
contentWidth: maxWidth,
|
|
83
|
+
tagsStyles: _.omitBy(tagStyles, tagStyle => !tagStyle),
|
|
84
|
+
systemFonts: [...defaultSystemFonts, style.baseFont.fontFamily],
|
|
85
|
+
baseStyle: style.baseFont,
|
|
86
|
+
ignoredStyles: ['fontFamily', 'letterSpacing', 'transform'],
|
|
293
87
|
renderers: customRenderers,
|
|
88
|
+
renderersProps: {
|
|
89
|
+
table: {
|
|
90
|
+
cssRules,
|
|
91
|
+
},
|
|
92
|
+
a: { onPress: this.onLinkPress },
|
|
93
|
+
iframe: {
|
|
94
|
+
webViewProps: {
|
|
95
|
+
renderToHardwareTextureAndroid: true,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
customHTMLElementModels: {
|
|
100
|
+
table: tableModel,
|
|
101
|
+
iframe: iframeModel,
|
|
102
|
+
},
|
|
103
|
+
WebView,
|
|
294
104
|
ignoredTags: IGNORED_TAGS,
|
|
295
105
|
};
|
|
296
106
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Dimensions } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export function resolveMaxWidth(style) {
|
|
4
|
+
// parentContainerPadding - padding between screen & SimpleHtml. Parent container
|
|
5
|
+
// can have it's own padding. If it does, we have to include it in calculation,
|
|
6
|
+
// otherwise maxWidth will be more than max & images will go over the edge
|
|
7
|
+
const parentContainerPadding = style.outerPadding || 0;
|
|
8
|
+
const paddingValue =
|
|
9
|
+
style.container.paddingLeft ||
|
|
10
|
+
0 + style.container.paddingRight ||
|
|
11
|
+
0 + parentContainerPadding;
|
|
12
|
+
|
|
13
|
+
return Dimensions.get('window').width - paddingValue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function resolveDimensions(objectToResize, style) {
|
|
17
|
+
const { width, height } = objectToResize;
|
|
18
|
+
|
|
19
|
+
if (!width || !height) {
|
|
20
|
+
return { width, height };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const maxWidth = resolveMaxWidth(style);
|
|
24
|
+
const objectToResizeRatio = height / width;
|
|
25
|
+
const resolvedWidth = width > maxWidth ? maxWidth : width;
|
|
26
|
+
const resolvedHeight = Math.round(resolvedWidth * objectToResizeRatio);
|
|
27
|
+
|
|
28
|
+
return { width: resolvedWidth, height: resolvedHeight };
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shoutem/ui",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.0-rc.0",
|
|
4
4
|
"description": "Styleable set of components for React Native applications",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"lint": "eslint .",
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
"@shoutem/animation": "~0.15.0",
|
|
17
17
|
"@shoutem/eslint-config-react": "~1.0.6",
|
|
18
18
|
"@shoutem/theme": "~0.13.0",
|
|
19
|
+
"@native-html/iframe-plugin": "2.6.1",
|
|
20
|
+
"@native-html/table-plugin": "5.3.1",
|
|
19
21
|
"auto-bind": "4.0.0",
|
|
20
22
|
"babel-plugin-transform-decorators-legacy": "1.3.5",
|
|
21
23
|
"buffer": "5.6.0",
|
|
@@ -33,8 +35,7 @@
|
|
|
33
35
|
"react-native-lightbox-v2": "0.9.0",
|
|
34
36
|
"react-native-linear-gradient": "2.6.2",
|
|
35
37
|
"react-native-modal": "13.0.1",
|
|
36
|
-
"react-native-render-html": "
|
|
37
|
-
"react-native-render-html-table-bridge": "0.6.0",
|
|
38
|
+
"react-native-render-html": "6.3.4",
|
|
38
39
|
"react-native-svg": "12.3.0",
|
|
39
40
|
"react-native-svg-transformer": "1.0.0",
|
|
40
41
|
"react-native-toast-message": "2.1.5",
|
|
@@ -66,7 +67,7 @@
|
|
|
66
67
|
"path": "0.12.7",
|
|
67
68
|
"prettier": "1.19.1",
|
|
68
69
|
"react": "17.0.2",
|
|
69
|
-
"react-dom": "
|
|
70
|
+
"react-dom": "17.0.2",
|
|
70
71
|
"react-native": "0.68.2",
|
|
71
72
|
"react-test-renderer": "17.0.2"
|
|
72
73
|
},
|
package/theme.js
CHANGED
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
INCLUDE,
|
|
8
8
|
inverseColorBrightnessForAmount,
|
|
9
9
|
} from '@shoutem/theme';
|
|
10
|
+
import { getHomeIndicatorPadding } from './helpers/device-selector';
|
|
10
11
|
import {
|
|
11
|
-
IPHONE_X_HOME_INDICATOR_PADDING,
|
|
12
12
|
IPHONE_X_NOTCH_PADDING,
|
|
13
13
|
IPHONE_XR_NOTCH_PADDING,
|
|
14
14
|
NAVIGATION_BAR_HEIGHT,
|
|
@@ -856,6 +856,10 @@ export default () => {
|
|
|
856
856
|
'shoutem.ui.View': {
|
|
857
857
|
[INCLUDE]: ['commonVariants', 'guttersPadding'],
|
|
858
858
|
|
|
859
|
+
'.with-home-indicator-padding': {
|
|
860
|
+
paddingBottom: getHomeIndicatorPadding(),
|
|
861
|
+
},
|
|
862
|
+
|
|
859
863
|
'.horizontal': {
|
|
860
864
|
[INCLUDE]: ['horizontalFlexAlignment'],
|
|
861
865
|
flexDirection: 'row',
|
|
@@ -955,21 +959,19 @@ export default () => {
|
|
|
955
959
|
backgroundColor: resolveVariable('paperColor'),
|
|
956
960
|
},
|
|
957
961
|
|
|
962
|
+
// This was created with home indicator padding in mind. Use with-home-indicator-padding
|
|
963
|
+
// if you need bottom padding. For actual notch - top of the screen padding, use SafeAreaView component instead.
|
|
958
964
|
'.with-notch-padding': {
|
|
959
|
-
paddingBottom:
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
965
|
+
paddingBottom: getHomeIndicatorPadding(),
|
|
966
|
+
},
|
|
967
|
+
|
|
968
|
+
'.with-home-indicator-padding': {
|
|
969
|
+
paddingBottom: getHomeIndicatorPadding(),
|
|
964
970
|
},
|
|
965
971
|
|
|
966
972
|
'shoutem.ui.ListView': {
|
|
967
973
|
listContent: {
|
|
968
|
-
paddingBottom:
|
|
969
|
-
iPhoneX: IPHONE_X_HOME_INDICATOR_PADDING,
|
|
970
|
-
iPhoneXR: IPHONE_X_HOME_INDICATOR_PADDING,
|
|
971
|
-
default: 0,
|
|
972
|
-
}),
|
|
974
|
+
paddingBottom: getHomeIndicatorPadding(),
|
|
973
975
|
},
|
|
974
976
|
},
|
|
975
977
|
|
|
@@ -2060,56 +2062,11 @@ export default () => {
|
|
|
2060
2062
|
},
|
|
2061
2063
|
|
|
2062
2064
|
'shoutem.ui.Switch': {
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
marginVertical: 7,
|
|
2067
|
-
paddingHorizontal: 2,
|
|
2068
|
-
paddingVertical: 2,
|
|
2069
|
-
width: 32,
|
|
2070
|
-
|
|
2071
|
-
muteAnimation(driver) {
|
|
2072
|
-
return {
|
|
2073
|
-
backgroundColor: driver.interpolate({
|
|
2074
|
-
inputRange: [0, 1],
|
|
2075
|
-
outputRange: [
|
|
2076
|
-
inverseColorBrightnessForAmount(
|
|
2077
|
-
resolveVariable('paperColor'),
|
|
2078
|
-
15,
|
|
2079
|
-
),
|
|
2080
|
-
changeColorAlpha(
|
|
2081
|
-
resolveVariable('secondaryButtonBackgroundColor'),
|
|
2082
|
-
1,
|
|
2083
|
-
),
|
|
2084
|
-
],
|
|
2085
|
-
}),
|
|
2086
|
-
};
|
|
2087
|
-
},
|
|
2088
|
-
},
|
|
2089
|
-
|
|
2090
|
-
thumb: {
|
|
2091
|
-
backgroundColor: resolveVariable('paperColor'),
|
|
2092
|
-
borderRadius: 7,
|
|
2093
|
-
height: 14,
|
|
2094
|
-
width: 14,
|
|
2095
|
-
|
|
2096
|
-
turnAnimation(driver, { layout, animationOptions }) {
|
|
2097
|
-
const { x, width } = layout;
|
|
2098
|
-
return {
|
|
2099
|
-
transform: [
|
|
2100
|
-
{
|
|
2101
|
-
translateX: driver.interpolate({
|
|
2102
|
-
inputRange: [0, 1],
|
|
2103
|
-
outputRange: [
|
|
2104
|
-
0,
|
|
2105
|
-
animationOptions.containerWidth - width - 2 * x,
|
|
2106
|
-
],
|
|
2107
|
-
}),
|
|
2108
|
-
},
|
|
2109
|
-
],
|
|
2110
|
-
};
|
|
2111
|
-
},
|
|
2065
|
+
track: {
|
|
2066
|
+
false: resolveVariable('backgroundColor'),
|
|
2067
|
+
true: resolveVariable('featuredColor'),
|
|
2112
2068
|
},
|
|
2069
|
+
thumb: resolveVariable('paperColor'),
|
|
2113
2070
|
},
|
|
2114
2071
|
|
|
2115
2072
|
'shoutem.ui.DropDownMenu': {
|