Skip to content

GridListItem - horizontal alignment support #3001

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

Merged
merged 10 commits into from
Apr 9, 2024
Merged
25 changes: 19 additions & 6 deletions demo/src/screens/componentScreens/GridListScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,38 @@ import {
GridListItem
} from 'react-native-ui-lib';
import products from '../../data/products';
import {renderBooleanOption} from '../ExampleScreenPresenter';
import {renderBooleanOption, renderMultipleSegmentOptions} from '../ExampleScreenPresenter';

class GridListScreen extends Component {
state = {
orientation: Constants.orientation,
useGridListItem: false
useGridListItem: true,
horizontalAlignment: GridListItem.horizontalAlignment.left,
overlayText: false
};

renderHeader = () => {
return (
<View>
<Text h1 marginB-s5>
<Text h1 marginV-s3>
GridList
</Text>
{renderBooleanOption.call(this, 'UseGridListItem', 'useGridListItem')}
<Text h3 marginV-s2>
GridListItem props
</Text>
{renderMultipleSegmentOptions.call(this, 'Horizontal Alignment:', 'horizontalAlignment', [
{label: 'left', value: GridListItem.horizontalAlignment.left},
{label: 'center', value: GridListItem.horizontalAlignment.center},
{label: 'right', value: GridListItem.horizontalAlignment.right}
])}
{renderBooleanOption.call(this, 'Use overlay text:', 'overlayText')}
</View>
);
};

renderItem: GridListProps<(typeof products)[0]>['renderItem'] = ({item}) => {
const {useGridListItem} = this.state;
const {useGridListItem, horizontalAlignment, overlayText} = this.state;

if (useGridListItem) {
return (
Expand All @@ -41,8 +52,10 @@ class GridListScreen extends Component {
itemSize={{width: '100%', height: 200}}
imageProps={{source: {uri: item.mediaUrl}}}
title="Title"
subtitle="Subitle"
overlayText
subtitle="Subtile"
alignToStart
overlayText={overlayText}
horizontalAlignment={horizontalAlignment}
/>
);
} else {
Expand Down
6 changes: 6 additions & 0 deletions src/components/gridListItem/gridListItem.api.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
"description": "Custom container styling for inline text"
},
{"name": "alignToStart", "type": "boolean", "description": "Should content be align to start", "default": "center"},
{
"name": "horizontalAlignment",
"type": "HorizontalAlignment",
"description": "Content horizontal alignment",
"default": "center"
},
{"name": "containerStyle", "type": "ViewStyle", "description": "Custom container style"},
{"name": "onPress", "type": "TouchableOpacityProps['onPress']", "description": "The item's action handler"},
{
Expand Down
95 changes: 71 additions & 24 deletions src/components/gridListItem/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import _ from 'lodash';
import React, {Component} from 'react';
import {StyleProp, StyleSheet, ViewStyle} from 'react-native';
import memoize from 'memoize-one';
import * as Modifiers from '../../commons/modifiers';
import {Colors, Spacings, Typography} from 'style';
import View, {ViewProps} from '../view';
import TouchableOpacity, {TouchableOpacityProps} from '../touchableOpacity';
import Text from '../text';
import Image, {ImageProps} from '../image';

export enum HorizontalAlignment {
left = 'left',
center = 'center',
right = 'right'
}

export interface GridListItemProps {
/**
* Image props object for rendering an image item
Expand Down Expand Up @@ -85,6 +92,10 @@ export interface GridListItemProps {
* Should content be align to start (default is center)
*/
alignToStart?: boolean;
/**
* Content horizontal alignment (default is center)
*/
horizontalAlignment?: HorizontalAlignment | `${HorizontalAlignment}`;
/**
* Custom container style
*/
Expand All @@ -101,7 +112,7 @@ export interface GridListItemProps {
* Test ID for component
*/
testID?: string;
children?: React.ReactNode;
children?: React.ReactElement;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can be an array

}

interface RenderContentType {
Expand All @@ -120,6 +131,8 @@ interface RenderContentType {
class GridListItem extends Component<GridListItemProps> {
static displayName = 'GridListItem';

static horizontalAlignment = HorizontalAlignment;

static defaultProps = {
itemSize: 48
};
Expand All @@ -139,14 +152,35 @@ class GridListItem extends Component<GridListItemProps> {
return {width: itemSize as number, height: itemSize as number};
}

getHorizontalAlignmentStyle = memoize(horizontalAlignment => {
switch (horizontalAlignment) {
case HorizontalAlignment.left:
return {contentStyle: styles.contentAlignedToStart, containerStyle: styles.containerAlignedToStart};
case HorizontalAlignment.right:
return {contentStyle: styles.contentAlignedToEnd, containerStyle: styles.containerAlignedToEnd};
case HorizontalAlignment.center:
return {contentStyle: styles.contentAlignedToCenter, containerStyle: styles.containerAlignedToCenter};
default:
undefined;
}
});

renderContent({text, typography, color, numberOfLines = 1, style, testID}: RenderContentType) {
const {alignToStart} = this.props;
const {alignToStart, horizontalAlignment} = this.props;
const horizontalAlignmentStyle = this.getHorizontalAlignmentStyle(horizontalAlignment);
if (text) {
return (
<Text
testID={testID}
// @ts-ignore
style={[style, Typography[typography], color && {color}, alignToStart && styles.contentAlignedToStart]}
style={[
style,
//@ts-ignore
Typography[typography],
color && {color},
alignToStart && styles.contentAlignedToStart,
horizontalAlignmentStyle?.contentStyle
Copy link
Collaborator

Choose a reason for hiding this comment

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

{textAlign: HorizontalAlignment} - can replace this object, just place it before this line:
alignToStart && styles.contentAlignedToStart, to avoid overriding it

]}
numberOfLines={numberOfLines}
>
{text}
Expand All @@ -160,6 +194,7 @@ class GridListItem extends Component<GridListItemProps> {
testID,
imageProps,
alignToStart,
horizontalAlignment,
containerStyle,
containerProps = {},
renderCustomItem,
Expand All @@ -181,33 +216,33 @@ class GridListItem extends Component<GridListItemProps> {
onPress,
renderOverlay
} = this.props;
const hasPress = _.isFunction(onPress);
const hasOverlay = _.isFunction(renderOverlay);
const Container = hasPress ? TouchableOpacity : View;
const imageStyle = {...this.getItemSizeObj()};
const width = _.get(imageStyle, 'width');
const TextContainer = overlayText ? View : React.Fragment;
const textContainerStyle = overlayText ? {style: [styles.overlayText, overlayTextContainerStyle]} : null;
const imageBorderRadius = imageProps?.borderRadius;
const Container = onPress ? TouchableOpacity : View;
const itemSize = this.getItemSizeObj();
const {width} = itemSize;
const horizontalAlignmentStyle = this.getHorizontalAlignmentStyle(horizontalAlignment);
const textContainerStyle = overlayText && {
style: [styles.overlayText, overlayTextContainerStyle]
};
const {hitSlop, ...otherContainerProps} = containerProps; // eslint-disable-line

return (
<Container
style={[styles.container, alignToStart && styles.containerAlignedToStart, {width}, containerStyle]}
style={[
styles.container,
alignToStart && styles.containerAlignedToStart,
horizontalAlignmentStyle?.containerStyle,
{width},
containerStyle
]}
{...otherContainerProps}
onPress={hasPress ? this.onItemPress : undefined}
onPress={onPress && this.onItemPress}
accessible={renderCustomItem ? true : undefined}
{...Modifiers.extractAccessibilityProps(this.props)}
>
{imageProps && (
<View style={[{borderRadius: imageBorderRadius}, imageStyle]}>
<Image {...imageProps} style={[imageStyle, imageProps?.style]}/>
{children}
</View>
)}
{imageProps && <Image style={itemSize} {...imageProps} customOverlayContent={children}/>}
{!_.isNil(renderCustomItem) && <View style={{width}}>{renderCustomItem()}</View>}
{hasOverlay && <View style={[styles.overlay, this.getItemSizeObj()]}>{renderOverlay?.()}</View>}
<TextContainer {...textContainerStyle}>
{renderOverlay && <View style={[styles.overlay, itemSize]}>{renderOverlay()}</View>}
<View {...textContainerStyle}>
Copy link
Collaborator

Choose a reason for hiding this comment

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

You changed the TextContainer from View or Fragment to View. This can result in alignment issues (if you remember the Icon bug...). Why did you make this change?

{this.renderContent({
testID: `${testID}.title`,
text: title,
Expand All @@ -232,7 +267,7 @@ class GridListItem extends Component<GridListItemProps> {
numberOfLines: descriptionLines,
style: styles.description
})}
</TextContainer>
</View>
</Container>
);
}
Expand All @@ -246,6 +281,12 @@ const styles = StyleSheet.create({
containerAlignedToStart: {
alignItems: 'flex-start'
},
containerAlignedToCenter: {
alignItems: 'center'
},
containerAlignedToEnd: {
alignItems: 'flex-end'
},
title: {
marginTop: Spacings.s1,
textAlign: 'center',
Expand All @@ -262,15 +303,21 @@ const styles = StyleSheet.create({
contentAlignedToStart: {
textAlign: 'left'
},
contentAlignedToCenter: {
textAlign: 'center'
},
contentAlignedToEnd: {
textAlign: 'right'
},
overlay: {
position: 'absolute',
left: 0,
top: 0
},
overlayText: {
position: 'absolute',
bottom: 10,
left: 10
bottom: 0,
padding: Spacings.s3
}
});

Expand Down
2 changes: 1 addition & 1 deletion src/components/image/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export type ImageProps = Omit<RNImageProps, 'source'> &
/**
* Render an overlay with custom content
*/
customOverlayContent?: JSX.Element;
customOverlayContent?: React.ReactElement;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why the children type change is relevant to this PR?

/**
* Default image source in case of an error
*/
Expand Down
2 changes: 1 addition & 1 deletion src/components/overlay/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export type OverlayTypes = Pick<ImageProps, 'borderRadius'> & {
/**
* Custom overlay content to be rendered on top of the image
*/
customContent?: JSX.Element;
customContent?: React.ReactElement;
};

/**
Expand Down