Skip to content

Feat/ColorPicker updates #614

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 14 commits into from
Jan 16, 2020
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
6 changes: 3 additions & 3 deletions demo/src/screens/componentScreens/ColorSwatchScreen.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import _ from 'lodash';
import React, {Component} from 'react';
import {ScrollView} from 'react-native';
import {Colors, View, Text, ColorSwatch, ColorPalette} from 'react-native-ui-lib'; //eslint-disable-line
import {Constants, Colors, View, Text, ColorSwatch, ColorPalette} from 'react-native-ui-lib'; //eslint-disable-line


export default class ColorSwatchScreen extends Component {
Expand Down Expand Up @@ -50,14 +50,14 @@ export default class ColorSwatchScreen extends Component {
</View>

<Text marginT-20 text60 dark10>ColorPalette</Text>
<Text marginB-10 text70 style={{color}}>Slected Color: {color}</Text>
<Text marginB-10 text70 style={{color}}>Selected Color: {color}</Text>
<ColorPalette value={color} onValueChange={this.onValueChange} colors={this.colors}/>

<Text margin-10 text60 dark10>Scrollable</Text>
<ColorPalette value={color1} onValueChange={this.onValueChange1} colors={this.mainColors}/>

<Text margin-10 text60 dark10>Pagination</Text>
<ColorPalette numberOfRows={4} value={color2} onValueChange={this.onValueChange2} colors={this.allColors}/>
<ColorPalette numberOfRows={!Constants.isTablet ? 4 : undefined} value={color2} onValueChange={this.onValueChange2} colors={this.allColors}/>
</View>
</ScrollView>
);
Expand Down
3 changes: 3 additions & 0 deletions src/assets/icons/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export const icons = {
get checkSmall() {
return require('./check-small.png');
},
get plusSmall() {
return require('./plusSmall.png');
},
get search() {
return require('./search.png');
},
Expand Down
152 changes: 53 additions & 99 deletions src/components/colorPicker/ColorPalette.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,27 @@ import _ from 'lodash';
import PropTypes from 'prop-types';
import memoize from 'memoize-one';
import React from 'react';
import {StyleSheet, ScrollView, Animated, Easing, UIManager, findNodeHandle} from 'react-native';
import {StyleSheet, UIManager, findNodeHandle} from 'react-native';
import {Constants} from '../../helpers';
import {Colors} from '../../style';
import {PureBaseComponent} from '../../commons';
import View from '../view';
import Carousel from '../carousel';
import PageControl from '../pageControl';
import Image from '../image';
import ColorSwatch, {SWATCH_SIZE} from './ColorSwatch';
import ScrollBar from '../scrollBar';


const VERTICAL_PADDING = 16;
const HORIZONTAL_PADDING = 20;
const MINIMUM_MARGIN = 16;
const SCROLLABLE_HEIGHT = 92;
const GRADIENT_WIDTH = 27;
const DEFAULT_NUMBER_OF_ROWS = 3;
const gradientImage = () => require('./assets/gradient.png');

/**
* @description: A color palette component
* @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/ColorPickerScreen.js
* @notes: This is a screen width component
* @extends: ScrollView
*/
export default class ColorPalette extends PureBaseComponent {
static displayName = 'ColorPalette';
Expand All @@ -39,6 +36,10 @@ export default class ColorPalette extends PureBaseComponent {
* Style to pass the palette container
*/
containerStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
/**
* The container margins
*/
containerWidth: PropTypes.number,
/**
* Whether to use pagination when number of colors exceeds the number of rows
*/
Expand Down Expand Up @@ -80,12 +81,11 @@ export default class ColorPalette extends PureBaseComponent {

this.state = {
currentPage: 0,
scrollable: false,
gradientOpacity: new Animated.Value(0)
scrollable: false
};

this.carousel = React.createRef();
this.scrollView = React.createRef();
this.scrollBar = React.createRef();
this.initLocalVariables();
}

Expand All @@ -99,7 +99,7 @@ export default class ColorPalette extends PureBaseComponent {

onOrientationChanged = () => {
this.initLocalVariables();
this.setState({gradientOpacity: new Animated.Value(0)}); // only to trigger render
this.setState({orientation: Constants.orientation}); // only to trigger render
}

initLocalVariables() {
Expand Down Expand Up @@ -127,15 +127,21 @@ export default class ColorPalette extends PureBaseComponent {
return this.getUniqueColors(this.props.colors);
}

getUniqueColors = memoize((colors) => {
const c = _.map(colors, color => {
if (Colors.isTransparent(color)) {
return color;
}
return _.toUpper(color);
});
return _.uniq(c);
});
get containerWidth() {
return this.props.containerWidth || Constants.screenWidth;
}

getUniqueColors = memoize(
(colors) => {
const c = _.map(colors, color => {
if (Colors.isTransparent(color)) {
return color;
}
return _.toUpper(color);
});
return _.uniq(c);
}
);

getNumberOfRows() {
const {numberOfRows} = this.props;
Expand All @@ -154,7 +160,7 @@ export default class ColorPalette extends PureBaseComponent {
// additional items have the minimum width of the margin between them and the previous item's width
const additionalItemMinimumWidth = SWATCH_SIZE + MINIMUM_MARGIN;
// floor(space left / size of additional items)
itemsPerRow += Math.floor((Constants.screenWidth - firstItemWidth) / additionalItemMinimumWidth);
itemsPerRow += Math.floor((this.containerWidth - firstItemWidth) / additionalItemMinimumWidth);

return itemsPerRow;
}
Expand All @@ -170,26 +176,14 @@ export default class ColorPalette extends PureBaseComponent {
}

// Now that we have the itemsPerRow set, we can calculate the actual innerMargin
const remainingSpace = Constants.screenWidth - this.itemsPerRow * SWATCH_SIZE - 2 * HORIZONTAL_PADDING;
const remainingSpace = this.containerWidth - this.itemsPerRow * SWATCH_SIZE - 2 * HORIZONTAL_PADDING;
// With pagination - there's 1 less space than the number of items
const numberOfMargins = this.itemsPerRow - 1;
const margin = remainingSpace / numberOfMargins;
// We have to subtract something since otherwise some Android devices will overflow into the next line
return (margin - 0.001) / 2;
}

animateGradientOpacity = (offsetX, contentWidth) => {
const overflow = contentWidth - Constants.screenWidth;
const newValue = offsetX > 0 && offsetX >= overflow - 1 ? 0 : 1;

Animated.timing(this.state.gradientOpacity, {
toValue: newValue,
easing: Easing.inOut(Easing.linear),
duration: 300,
useNativeDriver: true
}).start();
};

scrollToSelected() {
const {scrollable, currentPage} = this.state;

Expand All @@ -199,18 +193,19 @@ export default class ColorPalette extends PureBaseComponent {
if (childRef) {
const handle = findNodeHandle(childRef);
if (handle) {
UIManager.measureLayoutRelativeToParent(handle, e => {
console.warn(e);
},
(x, y, w, h) => {
if (x + w > Constants.screenWidth) {
this.scrollView.current.scrollTo({
x: x + w + HORIZONTAL_PADDING - Constants.screenWidth,
y: 0,
animated: false
});
}
});
UIManager.measureLayoutRelativeToParent(handle,
e => {
console.warn(e);
},
(x, y, w, h) => {
if (x + w > this.containerWidth) {
this.scrollBar.current.scrollTo({
x: x + w + HORIZONTAL_PADDING - this.containerWidth,
y: 0,
animated: false
});
}
});
}
}
} else if (this.usePagination) {
Expand All @@ -219,16 +214,7 @@ export default class ColorPalette extends PureBaseComponent {
}

onContentSizeChange = contentWidth => {
if (contentWidth > Constants.screenWidth) {
this.setState({scrollable: true, gradientOpacity: new Animated.Value(1)});
}
};

onScroll = event => {
const {contentOffset, contentSize} = event.nativeEvent;
const offsetX = contentOffset.x;
const contentWidth = contentSize.width;
this.animateGradientOpacity(offsetX, contentWidth);
this.setState({scrollable: contentWidth > this.containerWidth, contentWidth});
};

onChangePage = index => {
Expand All @@ -245,25 +231,6 @@ export default class ColorPalette extends PureBaseComponent {
}, 0);
};

renderGradient() {
return (
<Animated.View
pointerEvents="none"
style={{
position: 'absolute',
right: 0,
opacity: this.state.gradientOpacity
}}
>
<Image
source={gradientImage()}
resizeMode={'stretch'}
style={{width: GRADIENT_WIDTH, height: SCROLLABLE_HEIGHT - 12, tintColor: Colors.white}}
/>
</Animated.View>
);
}

getHorizontalMargins = (index) => {
const isFirst = index === 0;
const isOnLeft = isFirst || index % this.itemsPerRow === 0;
Expand Down Expand Up @@ -300,7 +267,7 @@ export default class ColorPalette extends PureBaseComponent {
};

renderColorSwatch(color, index) {
const {animatedIndex} = this.props;
const {animatedIndex, testID} = this.props;

return (
<ColorSwatch
Expand All @@ -313,6 +280,7 @@ export default class ColorPalette extends PureBaseComponent {
animated={index === animatedIndex}
onPress={this.onValueChange}
ref={r => (this.itemsRefs[index] = r)}
testID={`${testID}-${color}`}
/>
);
}
Expand All @@ -334,49 +302,35 @@ export default class ColorPalette extends PureBaseComponent {
);
}

renderScroll() {
renderScrollableContent() {
const {containerStyle, ...others} = this.props;
const {scrollable} = this.state;
const {scrollable, contentWidth} = this.state;

return (
<ScrollView
ref={this.scrollView}
horizontal
showsHorizontalScrollIndicator={false}
<ScrollBar
ref={this.scrollBar}
style={[containerStyle, styles.scrollContainer]}
scrollEnabled={scrollable}
onContentSizeChange={this.onContentSizeChange}
onScroll={this.onScroll}
height={SCROLLABLE_HEIGHT}
containerProps={{width: !scrollable ? contentWidth : undefined}}
gradientHeight={SCROLLABLE_HEIGHT - 12}
>
{this.renderPalette(others, styles.scrollContent, this.colors)}
</ScrollView>
</ScrollBar>
);
}

renderScrollableContent() {
const {scrollable} = this.state;

if (scrollable) {
return (
<View row>
{this.renderScroll()}
{this.renderGradient()}
</View>
);
}
return this.renderScroll();
}

renderPaginationContent() {
const {containerStyle, loop, ...others} = this.props;
const {currentPage} = this.state;
const colorGroups = _.chunk(this.colors, this.itemsPerPage);

return (
<View center style={[containerStyle, styles.paginationContainer]}>
<Carousel loop={loop} onChangePage={this.onChangePage} ref={this.carousel}>
<Carousel migrate loop={loop} onChangePage={this.onChangePage} ref={this.carousel}>
{_.map(colorGroups, (colorsPerPage, index) => {
return this.renderPalette(others, {...styles.page, width: Constants.screenWidth}, colorsPerPage, index);
return this.renderPalette(others, {...styles.page, width: this.containerWidth}, colorsPerPage, index);
})}
</Carousel>
<PageControl
Expand All @@ -399,7 +353,7 @@ export default class ColorPalette extends PureBaseComponent {
const styles = StyleSheet.create({
paletteContainer: {
flexDirection: 'row',
alignItems: 'flex-start',
alignItems: 'center',
paddingLeft: HORIZONTAL_PADDING,
paddingVertical: VERTICAL_PADDING
},
Expand Down
9 changes: 6 additions & 3 deletions src/components/colorPicker/ColorSwatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import TouchableOpacity from '../touchableOpacity';
import Image from '../image';
import {PureBaseComponent} from '../../commons';
import {Colors} from '../../style';
import {Constants} from '../../helpers';


const transparentImage = require('./assets/transparentSwatch/TransparentSwatch.png');
const DEFAULT_SIZE = 36;
const DEFAULT_SIZE = Constants.isTablet ? 44 : 36;
export const SWATCH_MARGIN = 12;
export const SWATCH_SIZE = DEFAULT_SIZE;

Expand Down Expand Up @@ -144,7 +145,7 @@ export default class ColorSwatch extends PureBaseComponent {
onLayout={this.onLayout}
>
{Colors.isTransparent(color) && (
<Image source={transparentImage} style={this.styles.transparentImage} resizeMode={'center'}/>
<Image source={transparentImage} style={this.styles.transparentImage} resizeMode={'cover'}/>
)}
<Animated.Image
source={Assets.icons.check}
Expand Down Expand Up @@ -194,7 +195,9 @@ function createStyles({color = Colors.dark30}) {
borderColor: Colors.rgba(Colors.dark30, 0.2)
},
transparentImage: {
...StyleSheet.absoluteFillObject
...StyleSheet.absoluteFillObject,
width: DEFAULT_SIZE,
height: DEFAULT_SIZE
}
});
}
Loading