Skip to content

Support passing max item width to grid view #1640

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 9 commits into from
Nov 24, 2021
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
5 changes: 4 additions & 1 deletion demo/src/screens/componentScreens/GridViewScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,11 @@ class GridViewScreen extends Component {
<GridView items={pairs} numColumns={2}/>
<Text marginV-s5 text60BO>
Dynamic itemSize
<Text text90>
{' '} (Using maxItemWidth)
</Text>
</Text>
<GridView items={dynamicLayout} numColumns={3}/>
<GridView items={dynamicLayout} maxItemWidth={120}/>
<Text marginV-s5 text60BO>
OverlayText
</Text>
Expand Down
28 changes: 18 additions & 10 deletions generatedTypes/src/components/gridView/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ export interface GridViewProps {
*/
viewWidth?: number;
/**
* Number of items to show in a row
* Allow a responsive item width to the maximum item width
*/
maxItemWidth?: number;
/**
* Number of items to show in a row (ignored when passing maxItemWidth)
*/
numColumns?: number;
/**
Expand All @@ -29,38 +33,42 @@ export interface GridViewProps {
/**
* whether to keep the items initial size when orientation changes,
* in which case the apt number of columns will be calculated automatically.
* Irrelevant when passing maxItemWidth
*/
keepItemSize?: boolean;
}
interface State {
interface GridViewState {
viewWidth: number;
numColumns: number;
itemSize: number;
}
declare type ExistProps = GridViewProps & State;
/**
* @description: A auto-generated grid view that calculate item size according to given props
* @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/GridViewScreen.tsx
*/
declare class GridView extends UIComponent<GridViewProps, State> {
declare class GridView extends UIComponent<GridViewProps, GridViewState> {
static displayName: string;
static defaultProps: {
numColumns: number;
itemSpacing: number;
};
private itemSize?;
private dimensionsChangeListener;
constructor(props: ExistProps);
static getDerivedStateFromProps(nextProps: ExistProps, prevState: State): {
state: {
viewWidth: number;
numColumns: number;
itemSize: number;
};
static getDerivedStateFromProps(nextProps: GridViewProps, prevState: GridViewState): {
viewWidth: number;
numColumns: number | undefined;
} | null;
componentDidMount(): void;
componentWillUnmount(): void;
onOrientationChanged: () => void;
shouldUpdateItemSize(): boolean;
getDefaultViewWidth(): number;
getCalculatedNumOfColumns(): number;
getItemSize(): number | undefined;
getGridContainerWidth(): number;
calcNumberOfColumns(): number;
calcItemSize(): number;
getThemeColor(placeColor: string): any;
renderLastItemOverlay(): JSX.Element | undefined;
renderItem: (item: GridListItemProps, index: number) => JSX.Element;
Expand Down
109 changes: 58 additions & 51 deletions src/components/gridView/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import _ from 'lodash';
import React from 'react';
import {StyleSheet} from 'react-native';
// TODO: we should use asBaseComponent here instead of using UIComponent directly
import {Colors, Spacings} from 'style';
// TODO: we should use asBaseComponent here instead of using UIComponent directly
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don't you want to include this in your refactor?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

not at the moment..

import UIComponent from '../../commons/UIComponent';
import View from '../view';
import Text from '../text';
import {Constants} from 'helpers';
import GridListItem, {GridListItemProps} from '../gridListItem';
import {formatLastItemLabel} from '../../helpers/FormattingPresenter';

const DEFAULT_NUM_COLUMNS = 3;
const DEFAULT_ITEM_SPACINGS = Spacings.s4;
export interface GridViewProps {
/**
* The list of items based on GridListItem props
Expand All @@ -20,7 +22,11 @@ export interface GridViewProps {
*/
viewWidth?: number;
/**
* Number of items to show in a row
* Allow a responsive item width to the maximum item width
*/
maxItemWidth?: number;
/**
* Number of items to show in a row (ignored when passing maxItemWidth)
*/
numColumns?: number;
/**
Expand All @@ -38,44 +44,38 @@ export interface GridViewProps {
/**
* whether to keep the items initial size when orientation changes,
* in which case the apt number of columns will be calculated automatically.
* Ignored when passing 'maxItemWidth'
*/
keepItemSize?: boolean;
}

interface State {
interface GridViewState {
viewWidth: number;
numColumns: number;
itemSize: number;
}


type ExistProps = GridViewProps & State
/**
* @description: A auto-generated grid view that calculate item size according to given props
* @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/GridViewScreen.tsx
*/
class GridView extends UIComponent<GridViewProps, State> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why you're not passing 'State'?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed

class GridView extends UIComponent<GridViewProps, GridViewState> {
static displayName = 'GridView';

static defaultProps = {
numColumns: 3,
itemSpacing: Spacings.s4
numColumns: DEFAULT_NUM_COLUMNS,
itemSpacing: DEFAULT_ITEM_SPACINGS
};

private itemSize?: number;
private dimensionsChangeListener: any;

constructor(props: ExistProps) {
super(props);

this.state = {
viewWidth: Math.floor(props.viewWidth || this.getDefaultViewWidth()),
numColumns: props.numColumns
};

this.itemSize = undefined;
}
state = {
viewWidth: this.getGridContainerWidth(),
numColumns: this.calcNumberOfColumns(),
itemSize: this.calcItemSize()
};

static getDerivedStateFromProps(nextProps: ExistProps, prevState: State) {
static getDerivedStateFromProps(nextProps: GridViewProps, prevState: GridViewState) {
let viewWidth;
let numColumns;
if (nextProps.viewWidth && Math.floor(nextProps.viewWidth) !== prevState.viewWidth) {
Expand All @@ -101,38 +101,44 @@ class GridView extends UIComponent<GridViewProps, State> {
}

onOrientationChanged = () => {
const {keepItemSize} = this.props;
const {itemSize} = this.state;

if (!this.props.viewWidth) {
this.setState({viewWidth: Math.floor(this.getDefaultViewWidth()), numColumns: this.getCalculatedNumOfColumns()});
const newItemSize = keepItemSize ? itemSize : this.calcItemSize();
this.setState({
viewWidth: Math.floor(this.getDefaultViewWidth()),
numColumns: this.calcNumberOfColumns(),
itemSize: newItemSize
});
}
};

shouldUpdateItemSize() {
return !this.itemSize || !this.props.keepItemSize;
getDefaultViewWidth() {
return Constants.screenWidth - Spacings.s5 * 2;
}

getDefaultViewWidth() {
// @ts-ignore
return Constants.screenWidth - (Spacings.page * 2);
getGridContainerWidth() {
return Math.floor(this.props.viewWidth || this.getDefaultViewWidth());
}

getCalculatedNumOfColumns() {
const {itemSpacing, numColumns} = this.props as ExistProps;
calcNumberOfColumns() {
const {numColumns, itemSpacing = DEFAULT_ITEM_SPACINGS, maxItemWidth} = this.props;
const containerWidth = this.getGridContainerWidth();

if (!this.shouldUpdateItemSize() && Constants.orientation === 'landscape' && this.itemSize && itemSpacing) {
const numberOfColumns = this.getDefaultViewWidth() / (this.itemSize + itemSpacing);
return Math.floor(numberOfColumns);
if (maxItemWidth) {
return Math.ceil((containerWidth + itemSpacing) / (maxItemWidth + itemSpacing));
} else {
return numColumns || DEFAULT_NUM_COLUMNS;
}
return numColumns;
}

getItemSize() {
const {itemSpacing} = this.props;
const {viewWidth, numColumns} = this.state;
calcItemSize() {
const {itemSpacing = DEFAULT_ITEM_SPACINGS} = this.props;
const containerWidth = this.getGridContainerWidth();
const numColumns = this.calcNumberOfColumns();

if (this.shouldUpdateItemSize() && viewWidth && itemSpacing && numColumns) {
return (viewWidth - itemSpacing * (numColumns - 1)) / numColumns;
}
return this.itemSize;
return (containerWidth - itemSpacing * (numColumns - 1)) / numColumns;
}

getThemeColor(placeColor: string) {
Expand All @@ -154,10 +160,7 @@ class GridView extends UIComponent<GridViewProps, State> {
return;
}

const imageBorderRadius = _.chain(items)
.first()
.get('imageProps.borderRadius')
.value();
const imageBorderRadius = _.chain(items).first().get('imageProps.borderRadius').value();
return (
<View
style={[
Expand All @@ -173,36 +176,40 @@ class GridView extends UIComponent<GridViewProps, State> {
}

renderItem = (item: GridListItemProps, index: number) => {
const {itemSize} = this.state;
const {items, itemSpacing} = this.props;
const {numColumns} = this.state;

const {numColumns = DEFAULT_NUM_COLUMNS} = this.state;
const itemsCount = _.size(items);
const rowCount = itemsCount / numColumns;
const isLastItemInRow = (index + 1) % numColumns === 0;
const isLastRow = index + 1 > (rowCount - 1) * numColumns;
const isLastItem = index === itemsCount - 1;
const size = typeof item.itemSize === 'object'
? {width: this.itemSize, height: _.get(item.itemSize, 'height', this.itemSize)}
: this.itemSize;
const size =
typeof item.itemSize === 'object' ? {width: itemSize, height: item.itemSize?.height || itemSize} : itemSize;
return (
<GridListItem
key={index}
{...item}
itemSize={size}
containerStyle={[!isLastItemInRow && {marginRight: itemSpacing},
!isLastRow && {marginBottom: itemSpacing}, item.containerStyle]}
containerStyle={[
!isLastItemInRow && {marginRight: itemSpacing},
!isLastRow && {marginBottom: itemSpacing},
item.containerStyle
]}
>
{isLastItem && this.renderLastItemOverlay()}
</GridListItem>
);
};

render() {
const {itemSize} = this.state;
const {items, viewWidth} = this.props;
this.itemSize = this.getItemSize();

return (
<View style={[styles.container, {width: viewWidth ? Math.floor(viewWidth) : undefined}]}>
{this.itemSize && _.map(items, this.renderItem)}
{itemSize && _.map(items, this.renderItem)}
</View>
);
}
Expand Down