Skip to content

Commit 1e52ecf

Browse files
committed
feat: introduce IterableEmbeddedCard component with styling and functionality for embedded messaging
1 parent 1086555 commit 1e52ecf

8 files changed

Lines changed: 231 additions & 21 deletions

File tree

example/src/components/Embedded/Embedded.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const Embedded = () => {
1616
IterableEmbeddedMessage[]
1717
>([]);
1818
const [selectedViewType, setSelectedViewType] =
19-
useState<IterableEmbeddedViewType>(IterableEmbeddedViewType.Banner);
19+
useState<IterableEmbeddedViewType>(IterableEmbeddedViewType.Card);
2020

2121
const syncEmbeddedMessages = useCallback(() => {
2222
Iterable.embeddedManager.syncMessages();

src/core/classes/IterableApi.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,10 @@ export class IterableApi {
540540
*/
541541
static startEmbeddedImpression(messageId: string, placementId: number) {
542542
IterableLogger.log('startEmbeddedImpression: ', messageId, placementId);
543-
return RNIterableAPI.startEmbeddedImpression(messageId, placementId);
543+
return RNIterableAPI.startEmbeddedImpression(
544+
messageId,
545+
Number(placementId)
546+
);
544547
}
545548

546549
/**

src/core/images/logo-grey.png

3.89 KB
Loading

src/embedded/components/IterableEmbeddedCard.tsx

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { StyleSheet } from 'react-native';
2+
import { embeddedMediaImageBackgroundColors } from '../../constants/embeddedViewDefaults';
3+
4+
export const IMAGE_HEIGHT = 230;
5+
export const PLACEHOLDER_IMAGE_HEIGHT = 56;
6+
export const PLACEHOLDER_IMAGE_WIDTH = 56;
7+
8+
export const styles = StyleSheet.create({
9+
body: {
10+
alignSelf: 'stretch',
11+
fontSize: 14,
12+
fontWeight: '400',
13+
lineHeight: 20,
14+
},
15+
bodyContainer: {
16+
alignItems: 'flex-start',
17+
alignSelf: 'stretch',
18+
display: 'flex',
19+
flexDirection: 'column',
20+
gap: 24,
21+
paddingBottom: 16,
22+
paddingHorizontal: 16,
23+
paddingTop: 12,
24+
},
25+
button: {
26+
borderRadius: 32,
27+
gap: 8,
28+
},
29+
buttonContainer: {
30+
alignItems: 'flex-start',
31+
alignSelf: 'stretch',
32+
display: 'flex',
33+
flexDirection: 'row',
34+
gap: 12,
35+
width: '100%',
36+
},
37+
buttonText: {
38+
fontSize: 14,
39+
fontWeight: '700',
40+
lineHeight: 20,
41+
},
42+
container: {
43+
alignItems: 'center',
44+
borderStyle: 'solid',
45+
boxShadow:
46+
'0 1px 1px 0 rgba(0, 0, 0, 0.06), 0 0 2px 0 rgba(0, 0, 0, 0.06), 0 0 1px 0 rgba(0, 0, 0, 0.08)',
47+
display: 'flex',
48+
flexDirection: 'column',
49+
gap: 16,
50+
justifyContent: 'center',
51+
overflow: 'hidden',
52+
width: '100%',
53+
},
54+
mediaContainer: {
55+
alignItems: 'flex-start',
56+
alignSelf: 'stretch',
57+
backgroundColor: embeddedMediaImageBackgroundColors.card,
58+
display: 'flex',
59+
flexDirection: 'row',
60+
height: IMAGE_HEIGHT,
61+
},
62+
mediaContainerNoImage: {
63+
alignItems: 'center',
64+
justifyContent: 'center',
65+
},
66+
mediaImage: {
67+
height: IMAGE_HEIGHT,
68+
paddingHorizontal: 0,
69+
paddingVertical: 0,
70+
width: '100%',
71+
},
72+
mediaImagePlaceholder: {
73+
height: PLACEHOLDER_IMAGE_HEIGHT,
74+
opacity: 0.25,
75+
width: PLACEHOLDER_IMAGE_WIDTH,
76+
},
77+
textContainer: {
78+
alignItems: 'flex-start',
79+
alignSelf: 'stretch',
80+
display: 'flex',
81+
flexDirection: 'column',
82+
gap: 8,
83+
},
84+
title: {
85+
fontSize: 18,
86+
fontWeight: '700',
87+
},
88+
});
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import {
2+
Image,
3+
PixelRatio,
4+
Text,
5+
TouchableOpacity,
6+
View,
7+
type TextStyle,
8+
type ViewStyle,
9+
Pressable,
10+
} from 'react-native';
11+
12+
import { IterableEmbeddedViewType } from '../../enums';
13+
import { useEmbeddedView } from '../../hooks/useEmbeddedView';
14+
import type { IterableEmbeddedComponentProps } from '../../types/IterableEmbeddedComponentProps';
15+
import { IMAGE_HEIGHT, styles } from './IterableEmbeddedCard.styles';
16+
17+
/**
18+
* TODO: Add default action click handler. See IterableEmbeddedView for functionality.
19+
*/
20+
21+
export const IterableEmbeddedCard = ({
22+
config,
23+
message,
24+
onButtonClick = () => {},
25+
onMessageClick = () => {},
26+
}: IterableEmbeddedComponentProps) => {
27+
const {
28+
componentRef,
29+
handleButtonClick,
30+
handleLayout,
31+
handleMessageClick,
32+
media,
33+
parsedStyles,
34+
} = useEmbeddedView(IterableEmbeddedViewType.Card, {
35+
message,
36+
config,
37+
onButtonClick,
38+
onMessageClick,
39+
});
40+
const buttons = message?.elements?.buttons ?? [];
41+
42+
return (
43+
<Pressable onPress={() => handleMessageClick()}>
44+
<View
45+
ref={componentRef}
46+
focusable={true}
47+
removeClippedSubviews={true}
48+
onLayout={handleLayout}
49+
style={[
50+
styles.container,
51+
{
52+
backgroundColor: parsedStyles.backgroundColor,
53+
borderColor: parsedStyles.borderColor,
54+
borderRadius: parsedStyles.borderCornerRadius,
55+
borderWidth: parsedStyles.borderWidth,
56+
} as ViewStyle,
57+
]}
58+
>
59+
<View
60+
style={[
61+
styles.mediaContainer,
62+
media.shouldShow ? {} : styles.mediaContainerNoImage,
63+
]}
64+
>
65+
<Image
66+
source={
67+
media.shouldShow
68+
? {
69+
uri: media.url as string,
70+
height: PixelRatio.getPixelSizeForLayoutSize(IMAGE_HEIGHT),
71+
}
72+
: // eslint-disable-next-line @typescript-eslint/no-require-imports
73+
require('../../../core/images/logo-grey.png')
74+
}
75+
style={
76+
media.shouldShow
77+
? styles.mediaImage
78+
: styles.mediaImagePlaceholder
79+
}
80+
alt={media.caption as string}
81+
/>
82+
</View>
83+
<View style={styles.bodyContainer}>
84+
<View style={styles.textContainer}>
85+
<Text
86+
style={[
87+
styles.title,
88+
{ color: parsedStyles.titleTextColor } as TextStyle,
89+
]}
90+
>
91+
{message.elements?.title}
92+
</Text>
93+
<Text
94+
style={[
95+
styles.body,
96+
{ color: parsedStyles.bodyTextColor } as TextStyle,
97+
]}
98+
>
99+
{message.elements?.body}
100+
</Text>
101+
</View>
102+
{buttons.length > 0 && (
103+
<View style={styles.buttonContainer}>
104+
{buttons.map((button, index) => {
105+
const backgroundColor =
106+
index === 0
107+
? parsedStyles.primaryBtnBackgroundColor
108+
: parsedStyles.secondaryBtnBackgroundColor;
109+
const textColor =
110+
index === 0
111+
? parsedStyles.primaryBtnTextColor
112+
: parsedStyles.secondaryBtnTextColor;
113+
return (
114+
<TouchableOpacity
115+
style={[styles.button, { backgroundColor } as ViewStyle]}
116+
onPress={() => handleButtonClick(button)}
117+
key={button.id}
118+
>
119+
<Text
120+
style={[
121+
styles.buttonText,
122+
{ color: textColor } as TextStyle,
123+
]}
124+
>
125+
{button.title}
126+
</Text>
127+
</TouchableOpacity>
128+
);
129+
})}
130+
</View>
131+
)}
132+
</View>
133+
</View>
134+
</Pressable>
135+
);
136+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './IterableEmbeddedCard';

src/embedded/components/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export * from './IterableEmbeddedBanner/IterableEmbeddedBanner';
2-
export * from './IterableEmbeddedCard';
2+
export * from './IterableEmbeddedCard/IterableEmbeddedCard';
33
export * from './IterableEmbeddedNotification/IterableEmbeddedNotification';
44
export * from './IterableEmbeddedView';

0 commit comments

Comments
 (0)