@quintype/native-components 2.30.5 → 2.30.7-beta.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/package.json +2 -2
- package/src/components/TwoColLayout/GridStoryCard.js +121 -0
- package/src/components/TwoColLayout/gridStyles.js +59 -0
- package/src/components/TwoColLayout/index.js +103 -0
- package/src/components/TwoColLayout/styles.js +16 -0
- package/src/components/index.js +2 -2
- package/src/utils/tableUtils.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quintype/native-components",
|
|
3
|
-
"version": "2.30.
|
|
3
|
+
"version": "2.30.7-beta.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
"react-native-is-detox": "^1.2.3",
|
|
23
23
|
"react-native-lightbox": "0.8.1",
|
|
24
24
|
"react-native-modal": "^13.0.1",
|
|
25
|
-
"react-native-render-html": "^4.2.3",
|
|
26
25
|
"react-native-share": "^10.2.0",
|
|
27
26
|
"react-native-star-rating-widget": "^1.7.3",
|
|
28
27
|
"react-native-track-player": "^4.1.1"
|
|
@@ -37,6 +36,7 @@
|
|
|
37
36
|
"react-native-fast-image": ">=8.3.2",
|
|
38
37
|
"react-native-linear-gradient": "^2.8.3",
|
|
39
38
|
"react-native-pdf": ">=6.7.4",
|
|
39
|
+
"react-native-render-html": ">=4.2.3",
|
|
40
40
|
"react-native-vector-icons": "^10.0.0",
|
|
41
41
|
"react-native-webview": ">=11.0.0"
|
|
42
42
|
},
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { get, throttle } from 'lodash';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import React, { useContext, memo } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
StyleSheet,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
View,
|
|
8
|
+
useWindowDimensions,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import {
|
|
11
|
+
AppTheme,
|
|
12
|
+
getImageMetadata,
|
|
13
|
+
getImageSlug,
|
|
14
|
+
} from '../../utils';
|
|
15
|
+
import { getStoryHeadline } from '../../utils/story';
|
|
16
|
+
import { ResponsiveImage, Text, StoryCardDetailsRow, StoryTemplateIcon } from '../index';
|
|
17
|
+
import { gridStoryStyles } from './gridStyles';
|
|
18
|
+
import {
|
|
19
|
+
COMP_GENERAL_CONSTANTS,
|
|
20
|
+
} from '../../constants/component-constants';
|
|
21
|
+
import PremiumIcons from '../../Icons/PremiumIcons/index';
|
|
22
|
+
|
|
23
|
+
const GridStoryCardBase = (props) => {
|
|
24
|
+
const { story = {} } = props;
|
|
25
|
+
const { theme } = useContext(AppTheme);
|
|
26
|
+
const {
|
|
27
|
+
COLORS,
|
|
28
|
+
premiumIcon,
|
|
29
|
+
FONT_SIZE,
|
|
30
|
+
storyCardOptions = {},
|
|
31
|
+
} = theme;
|
|
32
|
+
|
|
33
|
+
const styles = gridStoryStyles(theme);
|
|
34
|
+
const containerStyle = StyleSheet.flatten([
|
|
35
|
+
styles.container,
|
|
36
|
+
props.style,
|
|
37
|
+
]);
|
|
38
|
+
const hasShareButton = props.iconComponent && storyCardOptions.enableShareButton;
|
|
39
|
+
const headlineStyle = StyleSheet.flatten([
|
|
40
|
+
styles.headline,
|
|
41
|
+
!hasShareButton && styles.headlineWithoutIcon,
|
|
42
|
+
props.headlineStyle,
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
const isPremiumStory = story['access'] === 'subscription';
|
|
46
|
+
const throttledOnpress = throttle(props.onPress, 1000);
|
|
47
|
+
const screenWidth = useWindowDimensions().width;
|
|
48
|
+
const imageWidth = (screenWidth - (props.horizontalPadding || 24) * 2 - 10) / 2; // Rough estimate: screen - outer padding - gap / 2
|
|
49
|
+
const showDetailsRow = storyCardOptions.enableAuthorName
|
|
50
|
+
|| storyCardOptions.enablePublishedAt
|
|
51
|
+
|| storyCardOptions.enableReadTime;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<TouchableOpacity
|
|
55
|
+
testID={COMP_GENERAL_CONSTANTS.secondaryStoryCard}
|
|
56
|
+
onPress={throttledOnpress}
|
|
57
|
+
activeOpacity={0.8}
|
|
58
|
+
style={containerStyle}
|
|
59
|
+
>
|
|
60
|
+
<View style={styles.imageAndDetails}>
|
|
61
|
+
<ResponsiveImage
|
|
62
|
+
style={styles.image}
|
|
63
|
+
metaData={getImageMetadata(story)}
|
|
64
|
+
slug={getImageSlug(story) || ''}
|
|
65
|
+
cdn={props.cdn || ''}
|
|
66
|
+
imageWidth={imageWidth}
|
|
67
|
+
>
|
|
68
|
+
<View style={styles.storyTypeContainer}>
|
|
69
|
+
<StoryTemplateIcon storyTemplate={story['story-template']} theme={theme} />
|
|
70
|
+
</View>
|
|
71
|
+
</ResponsiveImage>
|
|
72
|
+
|
|
73
|
+
<View style={styles.headlineAndTimestampBlockContainer}>
|
|
74
|
+
{storyCardOptions.enableSectionName && <Text numberOfLines={1} ellipsizeMode="tail" style={styles.sectionName}>{get(story, ['sections', 0, 'display-name'], '')}</Text>}
|
|
75
|
+
|
|
76
|
+
<View>
|
|
77
|
+
<Text
|
|
78
|
+
primary
|
|
79
|
+
numberOfLines={storyCardOptions.numberOfLinesForTitle || 3}
|
|
80
|
+
ellipsizeMode="tail"
|
|
81
|
+
style={headlineStyle}
|
|
82
|
+
testID={COMP_GENERAL_CONSTANTS.secondaryStoryHeadline}
|
|
83
|
+
>
|
|
84
|
+
{isPremiumStory && premiumIcon !== 'none' && <PremiumIcons style={styles.premiumIcon} name={premiumIcon} color={COLORS.primary} size={FONT_SIZE.h4} />}
|
|
85
|
+
{isPremiumStory && premiumIcon !== 'none' && ' '}
|
|
86
|
+
{getStoryHeadline(story)?.trim()}
|
|
87
|
+
</Text>
|
|
88
|
+
<View style={styles.shareIconContainer}>
|
|
89
|
+
{props.iconComponent}
|
|
90
|
+
</View>
|
|
91
|
+
</View>
|
|
92
|
+
|
|
93
|
+
{showDetailsRow && (
|
|
94
|
+
<StoryCardDetailsRow
|
|
95
|
+
authorName={get(story.authors, [0, 'name'])}
|
|
96
|
+
publishedAt={story["published-at"]}
|
|
97
|
+
readTime={story['read-time']}
|
|
98
|
+
/>
|
|
99
|
+
)}
|
|
100
|
+
</View>
|
|
101
|
+
</View>
|
|
102
|
+
</TouchableOpacity>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
GridStoryCardBase.propTypes = {
|
|
107
|
+
cdn: PropTypes.string.isRequired,
|
|
108
|
+
story: PropTypes.any.isRequired,
|
|
109
|
+
iconComponent: PropTypes.element,
|
|
110
|
+
horizontalPadding: PropTypes.number,
|
|
111
|
+
style: PropTypes.object,
|
|
112
|
+
onPress: PropTypes.func,
|
|
113
|
+
headlineStyle: PropTypes.object,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
GridStoryCardBase.defaultProps = {
|
|
117
|
+
cdn: '',
|
|
118
|
+
horizontalPadding: 12,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const GridStoryCard = memo(GridStoryCardBase);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export const gridStoryStyles = ({ COLORS, FONT_SIZE, FONT_FAMILY, lineHeightMultiplier, storyCardOptions = {} }) => StyleSheet.create({
|
|
4
|
+
container: {
|
|
5
|
+
flexDirection: 'column',
|
|
6
|
+
backgroundColor: COLORS.BRAND_WHITE,
|
|
7
|
+
marginBottom: 14,
|
|
8
|
+
alignItems: 'flex-start',
|
|
9
|
+
gap: 10,
|
|
10
|
+
width: '48%',
|
|
11
|
+
},
|
|
12
|
+
imageAndDetails: {
|
|
13
|
+
flexDirection: 'column',
|
|
14
|
+
width: '100%',
|
|
15
|
+
alignItems: 'flex-start',
|
|
16
|
+
gap: 10,
|
|
17
|
+
},
|
|
18
|
+
image: {
|
|
19
|
+
marginTop: 4,
|
|
20
|
+
width: '100%',
|
|
21
|
+
aspectRatio: 16 / 9,
|
|
22
|
+
},
|
|
23
|
+
headlineAndTimestampBlockContainer: {
|
|
24
|
+
flexShrink: 1,
|
|
25
|
+
flexGrow: 1,
|
|
26
|
+
width: '100%',
|
|
27
|
+
},
|
|
28
|
+
sectionName: {
|
|
29
|
+
color: COLORS.BRAND_1,
|
|
30
|
+
fontSize: FONT_SIZE.h4,
|
|
31
|
+
lineHeight: FONT_SIZE.h4 * lineHeightMultiplier,
|
|
32
|
+
},
|
|
33
|
+
headline: {
|
|
34
|
+
color: COLORS.BRAND_BLACK,
|
|
35
|
+
flexWrap: 'wrap',
|
|
36
|
+
fontSize: FONT_SIZE.h3,
|
|
37
|
+
fontFamily: FONT_FAMILY.primaryBold,
|
|
38
|
+
lineHeight: FONT_SIZE.h3 * lineHeightMultiplier,
|
|
39
|
+
marginBottom: 4,
|
|
40
|
+
paddingRight: 25,
|
|
41
|
+
},
|
|
42
|
+
headlineWithoutIcon: {
|
|
43
|
+
paddingRight: 0,
|
|
44
|
+
},
|
|
45
|
+
storyTypeContainer: {
|
|
46
|
+
position: 'absolute',
|
|
47
|
+
bottom: 0,
|
|
48
|
+
left: 0,
|
|
49
|
+
},
|
|
50
|
+
premiumIcon: {
|
|
51
|
+
marginTop: FONT_SIZE.h3 * 0.45,
|
|
52
|
+
marginRight: 3,
|
|
53
|
+
},
|
|
54
|
+
shareIconContainer: {
|
|
55
|
+
position: 'absolute',
|
|
56
|
+
right: 0,
|
|
57
|
+
top: 0,
|
|
58
|
+
}
|
|
59
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import get from 'lodash/get';
|
|
2
|
+
import isEmpty from 'lodash/isEmpty';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import React, { memo, useContext } from 'react';
|
|
5
|
+
import { View } from 'react-native';
|
|
6
|
+
import {
|
|
7
|
+
CollectionTitle,
|
|
8
|
+
PrimaryStoryCard,
|
|
9
|
+
ShareButton,
|
|
10
|
+
} from '../index';
|
|
11
|
+
import { GridStoryCard } from './GridStoryCard';
|
|
12
|
+
import { AppTheme } from '../../utils';
|
|
13
|
+
import { collectionStyles } from './styles';
|
|
14
|
+
|
|
15
|
+
const TwoColLayoutBase = ({
|
|
16
|
+
cdn,
|
|
17
|
+
collectionName,
|
|
18
|
+
stories,
|
|
19
|
+
onCollectionPress,
|
|
20
|
+
onStoryPress,
|
|
21
|
+
collectionTestID,
|
|
22
|
+
offset,
|
|
23
|
+
horizontalPadding,
|
|
24
|
+
initialOffset,
|
|
25
|
+
hideCollectionTitle,
|
|
26
|
+
}) => {
|
|
27
|
+
const { theme } = useContext(AppTheme);
|
|
28
|
+
const styles = collectionStyles(theme);
|
|
29
|
+
|
|
30
|
+
const sliceLimit = offset || initialOffset || 5;
|
|
31
|
+
const primaryStory = !isEmpty(stories) && get(stories, [0, 'story'], stories[0]);
|
|
32
|
+
|
|
33
|
+
// Secondary stories start from index 1
|
|
34
|
+
const secondaryStories = stories.slice(1, sliceLimit);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
38
|
+
{!hideCollectionTitle && (
|
|
39
|
+
<CollectionTitle
|
|
40
|
+
title={collectionName}
|
|
41
|
+
onPress={onCollectionPress}
|
|
42
|
+
horizontalPadding={horizontalPadding}
|
|
43
|
+
/>
|
|
44
|
+
)}
|
|
45
|
+
<PrimaryStoryCard
|
|
46
|
+
collectionTestID={collectionTestID}
|
|
47
|
+
onPress={() => {
|
|
48
|
+
!isEmpty(stories) && onStoryPress(primaryStory);
|
|
49
|
+
}}
|
|
50
|
+
cdn={cdn}
|
|
51
|
+
story={!isEmpty(stories) ? primaryStory : {}}
|
|
52
|
+
iconComponent={<ShareButton story={primaryStory} inListingStoryCard />}
|
|
53
|
+
horizontalPadding={horizontalPadding}
|
|
54
|
+
/>
|
|
55
|
+
|
|
56
|
+
{/* Grid Container for Secondary Stories */}
|
|
57
|
+
<View style={[styles.gridContainer, { marginHorizontal: horizontalPadding }]}>
|
|
58
|
+
{secondaryStories.map((currentStory, index) => {
|
|
59
|
+
const secondaryStory = get(currentStory, ['story'], currentStory);
|
|
60
|
+
return (
|
|
61
|
+
<React.Fragment key={secondaryStory?.id}>
|
|
62
|
+
<GridStoryCard
|
|
63
|
+
collectionTestID={collectionTestID}
|
|
64
|
+
onPress={() => onStoryPress(secondaryStory)}
|
|
65
|
+
cdn={cdn}
|
|
66
|
+
story={secondaryStory}
|
|
67
|
+
iconComponent={<ShareButton story={secondaryStory} inListingStoryCard />}
|
|
68
|
+
horizontalPadding={0} // Padding handled by parent container margin
|
|
69
|
+
/>
|
|
70
|
+
{((index + 1) % 2 === 0) && index !== secondaryStories.length - 1 && (
|
|
71
|
+
<View style={[styles.separator, { marginBottom: 16 }]} />
|
|
72
|
+
)}
|
|
73
|
+
</React.Fragment>
|
|
74
|
+
);
|
|
75
|
+
})}
|
|
76
|
+
</View>
|
|
77
|
+
{!isEmpty(stories) && (
|
|
78
|
+
<View style={{ paddingHorizontal: horizontalPadding }}>
|
|
79
|
+
<View style={styles.separator} />
|
|
80
|
+
</View>
|
|
81
|
+
)}
|
|
82
|
+
</>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
TwoColLayoutBase.propTypes = {
|
|
87
|
+
offset: PropTypes.number,
|
|
88
|
+
initialOffset: PropTypes.number,
|
|
89
|
+
cdn: PropTypes.string.isRequired,
|
|
90
|
+
collectionName: PropTypes.string,
|
|
91
|
+
stories: PropTypes.array.isRequired,
|
|
92
|
+
onCollectionPress: PropTypes.func,
|
|
93
|
+
onStoryPress: PropTypes.func,
|
|
94
|
+
collectionTestID: PropTypes.string,
|
|
95
|
+
horizontalPadding: PropTypes.number,
|
|
96
|
+
hideCollectionTitle: PropTypes.bool,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
TwoColLayoutBase.defaultProps = {
|
|
100
|
+
horizontalPadding: 12,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const TwoColLayout = memo(TwoColLayoutBase);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export const collectionStyles = ({ COLORS = {} }) => StyleSheet.create({
|
|
4
|
+
gridContainer: {
|
|
5
|
+
flexDirection: 'row',
|
|
6
|
+
flexWrap: 'wrap',
|
|
7
|
+
justifyContent: 'space-between',
|
|
8
|
+
marginTop: 16,
|
|
9
|
+
},
|
|
10
|
+
separator: {
|
|
11
|
+
width: '100%',
|
|
12
|
+
height: 0.8,
|
|
13
|
+
opacity: 0.1,
|
|
14
|
+
backgroundColor: COLORS.BRAND_BLACK,
|
|
15
|
+
}
|
|
16
|
+
});
|
package/src/components/index.js
CHANGED
|
@@ -44,5 +44,5 @@ export { StoryTemplateIcon } from './StoryTemplateIcon';
|
|
|
44
44
|
export { StoryCardDetailsRow } from './StoryCardDetailsRow';
|
|
45
45
|
export { TextA } from './TextA';
|
|
46
46
|
export { TextQ } from './TextQ';
|
|
47
|
-
export {Comments} from './Comments';
|
|
48
|
-
|
|
47
|
+
export { Comments } from './Comments';
|
|
48
|
+
export { TwoColLayout } from './TwoColLayout';
|
package/src/utils/tableUtils.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
2
|
|
|
3
3
|
export const formatData = (content) => {
|
|
4
|
-
const dataArray = content.split(/\n/);
|
|
4
|
+
const dataArray = content.split(/\n/).filter(line => line.trim() !== '');
|
|
5
5
|
const headerRowData = get(dataArray, [0], '');
|
|
6
6
|
const headerFields = headerRowData.split(',').map((headerValue) => headerValue.trim());
|
|
7
|
-
const dataFields = dataArray.slice(1
|
|
7
|
+
const dataFields = dataArray.slice(1);
|
|
8
8
|
const headerData = dataFields.map((dataField) => dataField.split(',').reduce((acc, currEle, index) => {
|
|
9
9
|
acc[get(headerFields, [index], '').trim()] = currEle.trim();
|
|
10
10
|
return acc;
|