Skip to content

Instantly share code, notes, and snippets.

@rahulpunchh
Last active August 13, 2026 12:29
Show Gist options
  • Select an option

  • Save rahulpunchh/cb31ec932e2148eefff57c40d92e4ec7 to your computer and use it in GitHub Desktop.

Select an option

Save rahulpunchh/cb31ec932e2148eefff57c40d92e4ec7 to your computer and use it in GitHub Desktop.
OLO Review And Pay: restore Edit icons via SectionHeader trailingIcon (BasePaymentCell + CartList)

OLO: Review And Pay — restore Edit icons

Problem

After MF-3665, SectionHeader uses width: '100%'. OLO placed Edit as a sibling after SectionHeader, so the icon was pushed off-screen on Review And Pay.

Fix (OLO)

Wire Edit through RNPC SectionHeader trailingIcon API (far-right rail), not titleRightIcon (inline title adornment — used by Collectibles info on Rewards).

Files

  • src/UI/Payment/BasePaymentCell.js
  • src/UI/Payment/CartList/CartList.js

Depends on RNPC

SectionHeader must expose:

  • trailingIcon
  • trailingIconStyle
  • trailingIconTintColor
  • trailingIconAccessibilityLabel
  • onTrailingIconPress

(titleRightIcon stays inline for Rewards Collectibles.)

Apply

Copy these files into olo-framework-reactnative (app-studio / current branch), commit, and bump the master-app git ref / reinstall.

Paths in package

src/UI/Payment/BasePaymentCell.js
src/UI/Payment/CartList/CartList.js
//
// Copyright © 2017-Present, Punchh, Inc.
// All rights reserved.
//
'use strict';
import React from 'react';
import PropTypes from 'prop-types';
import { View, Animated } from 'react-native';
import { SectionHeader, BaseComponent } from 'react-native-punchh-components';
const AnimatedView = Animated.createAnimatedComponent(View);
export default class BasePaymentCell extends BaseComponent {
renderTitle() {
const styles = this.styleSheet();
const editImage = this.appImages('edit');
return (
<View style={styles.headerStyle}>
{!!this.props.flashColor && (
<AnimatedView
pointerEvents="none"
style={[styles.flashOverlayStyle, { backgroundColor: this.props.flashColor }]}
/>
)}
<SectionHeader
title={this.props.title}
titleTextStyle={[styles.sectionTitle, this.props.titleStyle]}
trailingIcon={this.props.onEditPress ? editImage.source : null}
trailingIconStyle={[editImage.style, styles.editIconStyle]}
trailingIconTintColor={styles.imageTintColor}
trailingIconAccessibilityLabel={this.ls('accessibility_editIcon')}
onTrailingIconPress={this.props.onEditPress}
/>
</View>
);
}
render() {
const styles = this.styleSheet();
return (
<View style={styles.container}>
{this.renderTitle()}
<View
style={[styles.childContainer, this.props.detailContainerStyles]}
accessible={this.props.accessible ?? true}
>
{this.props.children}
</View>
</View>
);
}
defaultStyles() {
const { Typography, Colors } = this.theme();
return {
childContainer: {
paddingHorizontal: Typography.xTiny,
paddingVertical: Typography.regular
},
headerStyle: {
backgroundColor: Colors.SECTION_HEADER_BG
},
flashOverlayStyle: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 100
},
imageTintColor: Colors.SECTION_HEADER_IMAGE_TINT_COLOR,
editIconStyle: {
marginRight: Typography.small,
width: 20,
height: 20
},
sectionTitle: {}
};
}
customStyles() {
return this.theme().Payment.BasePaymentCell?.call(this, this.props);
}
}
BasePaymentCell.propTypes = {
title: PropTypes.string,
titleStyle: PropTypes.object,
flashColor: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
onEditPress: PropTypes.func,
detailContainerStyles: PropTypes.object,
children: PropTypes.node.isRequired
};
BasePaymentCell.defaultProps = {
title: '',
titleStyle: {},
onEditPress: () => null,
detailContainerStyles: {}
};
//
// Copyright © 2022-Present, Punchh, Inc.
// All rights reserved.
//
'use strict';
import React from 'react';
import { BaseComponent, SectionHeader, PunchhText } from 'react-native-punchh-components';
import { View } from 'react-native';
export class CartList extends BaseComponent {
renderTitle(styles, products) {
const { headerStyle } = this.styleSheet();
const itemCount = products?.length;
const itemText = ' (' + `${itemCount} ${itemCount === 1 ? this.ls('item') : this.ls('items')}` + ')';
const title = this.ls('basketHeading') + itemText;
const editImage = this.appImages('edit');
return (
<View style={headerStyle}>
<SectionHeader
title={title}
titleTextStyle={styles.sectionTitle}
trailingIcon={editImage.source}
trailingIconStyle={[editImage.style, styles.editIconStyle]}
trailingIconTintColor={styles.imageTintColor}
trailingIconAccessibilityLabel={this.ls('accessibility_editIcon')}
onTrailingIconPress={() => this.props.onEditPress()}
/>
</View>
);
}
renderList(products) {
const productCount = products?.length;
return products.map((item, index) => this.renderRow(item, index, productCount));
}
renderRow(item, index, productCount) {
const styles = this.styleSheet();
const isLastItem = index == productCount - 1;
let choices = null;
if (item.choices && item.choices?.length) {
choices = item.choices
.filter(i => i.name !== '-')
.map(i => (i.quantity > 1 ? `${i.name.trim()} (${i.quantity})` : i.name.trim()))
.join(', ');
}
return (
<View style={styles.infoContainerStyle}>
<View style={styles.titleView}>
<PunchhText
style={styles.quantityLabelStyle}
label={item.quantity}
accessibilityLabel={this.ls('accessibility_itemQuantity').sformat(item.quantity)}
/>
<View style={styles.verticalSepratorStyle} />
<View style={styles.itemContainerStyle}>
<PunchhText
type="bold"
style={styles.itemStyle}
label={item.name}
accessibilityLabel={this.ls('accessibility_itemName').sformat(item.name)}
/>
{choices && <PunchhText style={styles.subTitleStyle} label={choices} />}
</View>
<PunchhText
type="bold"
style={styles.amountStyle}
label={this.ls('$') + item.totalcost.toFixed(2)}
accessibilityLabel={this.ls('accessibility_itemPrice').sformat(item.totalcost.toFixed(2))}
/>
</View>
{!isLastItem && <View style={styles.sepratorStyle} />}
</View>
);
}
render() {
const styles = this.styleSheet();
if (this.props.basket?.products) {
return (
<View style={styles.container}>
{this.renderTitle(styles, this.props.basket?.products)}
{this.renderList(this.props.basket?.products)}
</View>
);
}
return <View />;
}
defaultStyles() {
const { Colors, Typography } = this.theme();
return {
headerStyle: {
backgroundColor: Colors.SECTION_HEADER_BG
},
infoContainerStyle: {
flex: 1,
minHeight: 48,
justifyContent: 'center'
},
titleView: { flexDirection: 'row', padding: Typography.regular },
itemStyle: {
paddingHorizontal: Typography.xxSmall
},
itemContainerStyle: {
flexDirection: 'column',
flex: 1
},
subTitleStyle: {
marginTop: Typography.xTiny,
paddingHorizontal: Typography.xxSmall
},
editIconStyle: {
marginRight: Typography.regular,
width: 20,
height: 20
},
verticalSepratorStyle: {
width: 0.5,
backgroundColor: Colors.LIGHT_SEPARATOR_COLOR
},
sepratorStyle: {
backgroundColor: Colors.LIGHT_SEPARATOR_COLOR,
height: 0.5
},
quantityLabelStyle: {
marginRight: Typography.xxSmall,
minWidth: 10
},
sectionTitle: {
fontSize: Typography.fontSize.small
},
amountStyle: {},
imageTintColor: Colors.SECTION_HEADER_IMAGE_TINT_COLOR
};
}
customStyles() {
return this.theme().Payment.CartList?.call(this, this.props);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment