Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(in-app-messaging): add BannerMessage UI component #9124

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright 2017-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/

import React from 'react';
import { Image, Text, View } from 'react-native';
import isEmpty from 'lodash/isEmpty';

import icons from '../../../icons';
import { useInAppMessageButtonStyle, useInAppMessageImage } from '../../hooks';
import { Button, IconButton } from '../../ui';

import { ICON_BUTTON_HIT_SLOP, ICON_BUTTON_SIZE } from '../constants';
import MessageWrapper from '../MessageWrapper';
import { styles } from './styles';
import { BannerMessageProps } from './types';

export default function BannerMessage({
body,
container,
header,
image,
layout,
onClose,
position,
primaryButton,
secondaryButton,
style,
}: BannerMessageProps) {
const { imageStyle, shouldDelayMessageRendering, shouldRenderImage } = useInAppMessageImage(image, layout);

const messageButtonStyle = { primaryButton: primaryButton?.style, secondaryButton: secondaryButton?.style };
const { primaryButtonStyle, secondaryButtonStyle } = useInAppMessageButtonStyle({
baseButtonStyle: styles,
messageButtonStyle,
overrideButtonStyle: style,
});

const hasPrimaryButton = !isEmpty(primaryButton);
const hasSecondaryButton = !isEmpty(secondaryButton);

return shouldDelayMessageRendering ? null : (
<MessageWrapper>
<View style={[styles.positionContainer, styles[position]]}>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This is not blocking but I wonder if styles should be defaultStyle or something? I keep finding myself having to remind myself what the difference between styles and style is. As a reader of foreign code, I would have no idea why they're different things.

Copy link
Member Author

@calebpollman calebpollman Oct 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo default styling as styles is a pretty standard convention in React Native. That said i do think all the styling applied in these components is not the easiest to parse 😟

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default styling as styles is a pretty standard convention in React Native

This is a good point too since static StyleSheets are typically assigned to a styles variable. It was just a thought

<View style={[styles.container, container?.style, style?.container]}>
<View style={styles.contentContainer}>
{shouldRenderImage && (
<View style={styles.imageContainer}>
<Image source={{ uri: image?.src }} style={imageStyle} />
</View>
)}
<View style={styles.textContainer}>
{header?.content && <Text style={[styles.header, header.style, style?.header]}>{header.content}</Text>}
{body?.content && <Text style={[styles.message, body.style, style?.message]}>{body.content}</Text>}
</View>
<IconButton
color={style?.closeIconColor}
hitSlop={ICON_BUTTON_HIT_SLOP}
onPress={onClose}
size={ICON_BUTTON_SIZE}
source={icons.close}
style={[styles.iconButton, style?.closeIconButton]}
/>
</View>
{(hasPrimaryButton || hasSecondaryButton) && (
<View style={styles.buttonsContainer}>
{hasSecondaryButton && (
<Button
onPress={secondaryButton.onPress}
style={secondaryButtonStyle.container}
textStyle={secondaryButtonStyle.text}
>
{secondaryButton.title}
</Button>
)}
{hasPrimaryButton && (
<Button
onPress={primaryButton.onPress}
style={primaryButtonStyle.container}
textStyle={primaryButtonStyle.text}
>
{primaryButton.title}
</Button>
)}
</View>
)}
</View>
</View>
</MessageWrapper>
);
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default } from './BannerMessage';
export { BannerMessageProps } from './types';
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright 2017-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/

import { StyleSheet } from 'react-native';

import { getLineHeight } from '../utils';
import {
BANNER_ELEVATION,
BANNER_SHADOW_HEIGHT,
BANNER_SHADOW_OPACITY,
BANNER_SHADOW_RADIUS,
BANNER_SHADOW_WIDTH,
BLACK,
BORDER_RADIUS_BASE,
FONT_SIZE_BASE,
FONT_SIZE_LARGE,
FONT_WEIGHT_BASE,
LIGHT_GREY,
SPACING_EXTRA_LARGE,
SPACING_LARGE,
SPACING_MEDIUM,
SPACING_SMALL,
WHITE,
} from '../constants';
import { BannerMessageStyle } from './types';

export const styles: BannerMessageStyle = StyleSheet.create({
// position style
positionContainer: {
flex: 1,
backgroundColor: 'transparent',
},
bottom: {
justifyContent: 'flex-end',
},
middle: {
justifyContent: 'center',
},
top: {
justifyContent: 'flex-start',
},

// shared style
buttonContainer: {
backgroundColor: LIGHT_GREY,
borderRadius: BORDER_RADIUS_BASE,
flex: 1,
margin: SPACING_MEDIUM,
padding: SPACING_LARGE,
},
buttonsContainer: {
flexDirection: 'row',
justifyContent: 'center',
paddingHorizontal: SPACING_SMALL,
},
buttonText: {
fontSize: FONT_SIZE_BASE,
fontWeight: FONT_WEIGHT_BASE,
lineHeight: getLineHeight(FONT_SIZE_BASE),
textAlign: 'center',
},
container: {
backgroundColor: WHITE,
elevation: BANNER_ELEVATION,
margin: SPACING_EXTRA_LARGE,
shadowColor: BLACK,
shadowOffset: {
width: BANNER_SHADOW_WIDTH,
height: BANNER_SHADOW_HEIGHT,
},
shadowOpacity: BANNER_SHADOW_OPACITY,
shadowRadius: BANNER_SHADOW_RADIUS,
},
contentContainer: {
flexDirection: 'row',
padding: SPACING_LARGE,
},
header: {
fontSize: FONT_SIZE_LARGE,
fontWeight: FONT_WEIGHT_BASE,
lineHeight: getLineHeight(FONT_SIZE_LARGE),
},
iconButton: {
alignSelf: 'flex-start',
marginLeft: 'auto',
},
imageContainer: {
justifyContent: 'center',
},
message: {
fontSize: FONT_SIZE_BASE,
fontWeight: FONT_WEIGHT_BASE,
lineHeight: getLineHeight(FONT_SIZE_BASE),
},
textContainer: {
marginHorizontal: SPACING_SMALL,
paddingLeft: SPACING_MEDIUM,
flex: 1,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,29 @@
* and limitations under the License.
*/

import { TextStyle, ViewStyle } from 'react-native';
import { InAppMessageComponentPosition, InAppMessageComponentBaseProps } from '../types';

export interface BannerMessageProps extends InAppMessageComponentBaseProps {
position: InAppMessageComponentPosition;
}

export interface BannerMessageStyle {
// position specific style
bottom: ViewStyle;
middle: ViewStyle;
top: ViewStyle;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the grouping approach you did here :)

// component style
buttonContainer: ViewStyle;
buttonsContainer: ViewStyle;
buttonText: TextStyle;
container: ViewStyle;
contentContainer: ViewStyle;
header: TextStyle;
iconButton: ViewStyle;
imageContainer: ViewStyle;
message: TextStyle;
positionContainer: ViewStyle;
textContainer: ViewStyle;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2017-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/

import React from 'react';
import { SafeAreaView } from 'react-native';

import { styles } from './styles';
import { MessageWrapperProps } from './types';

export default function MessageWrapper({ children, style }: MessageWrapperProps) {
return <SafeAreaView style={[styles.safeArea, style]}>{children}</SafeAreaView>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from './MessageWrapper';
export { MessageWrapperProps, MessageWrapperStyle } from './types';
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2017-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/

import { StyleSheet } from 'react-native';
import { MessageWrapperStyle } from './types';

export const styles: MessageWrapperStyle = StyleSheet.create({
safeArea: {
...StyleSheet.absoluteFillObject,
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2017-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/

import { ReactNode } from 'react';
import { StyleProp, ViewStyle } from 'react-native';

export interface MessageWrapperProps {
children: ReactNode;
style?: StyleProp<ViewStyle>;
}

export interface MessageWrapperStyle {
safeArea: ViewStyle;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2017-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/

/**
* Style constants either match or approximate the values used in the Pinpoint console preview.
* Some values, such as spacing, are slightly different to allow for a more mobile friendly UX
*/

// color
export const BLACK = '#000';
export const LIGHT_GREY = '#e8e8e8';
export const WHITE = '#fff';
Comment on lines +19 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I'm not yet familiar with the React Native codebase's patterns, but in the future, it could be good to prefix the color consts with COLOR_ - i.e. COLOR_BLACK, COLOR_WHITE, COLOR_GREY_LIGHT (similar to BANNER_) in order to group the declarations closer together.


// spacing
export const SPACING_SMALL = 4;
export const SPACING_MEDIUM = 8;
export const SPACING_LARGE = 12;
export const SPACING_EXTRA_LARGE = 16;

// border radius
export const BORDER_RADIUS_BASE = 4;

// font
export const FONT_SIZE_BASE = 16;
export const FONT_SIZE_LARGE = 18;

export const FONT_WEIGHT_BASE = '400';

export const LINE_HEIGHT_MULTIPLIER = 1.5;

// icon
export const ICON_BUTTON_SIZE = 20;
export const ICON_BUTTON_HIT_SLOP = 10;

// component specific constants

// BannerMessage
// iOS shadow values
export const BANNER_SHADOW_HEIGHT = 2;
export const BANNER_SHADOW_WIDTH = 2;
export const BANNER_SHADOW_OPACITY = 0.1;
export const BANNER_SHADOW_RADIUS = 2;

// android shadow values
export const BANNER_ELEVATION = 3;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { BannerMessageProps } from './BannerMessage';
export { default as BannerMessage, BannerMessageProps } from './BannerMessage';
export { CarouselMessageProps } from './CarouselMessage';
export { FullScreenMessageProps } from './FullScreenMessage';
export { default as InAppMessageDisplay } from './InAppMessageDisplay';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
*/

import { ColorValue, StyleProp, TextStyle, ViewStyle } from 'react-native';
import { InAppMessageButton, InAppMessageContent } from '@aws-amplify/notifications';

import { InAppMessageButton, InAppMessageContent, InAppMessageLayout } from '@aws-amplify/notifications';

export type InAppMessageComponentButtonStyle = {
container?: StyleProp<ViewStyle>;
Expand Down Expand Up @@ -49,6 +50,7 @@ export interface InAppMessageComponentContentProps
}

export interface InAppMessageComponentBaseProps extends InAppMessageComponentContentProps {
layout: InAppMessageLayout;
id: string;
onClose?: () => void;
style?: InAppMessageComponentStyle;
Expand Down
Loading