@wordpress/components
Version:
UI components for WordPress.
305 lines (275 loc) • 7.91 kB
JavaScript
/**
* External dependencies
*/
import {
ScrollView,
TouchableWithoutFeedback,
View,
Animated,
Easing,
Dimensions,
Platform,
Text,
} from 'react-native';
import { map, uniq } from 'lodash';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { useRef, useEffect, useState } from '@wordpress/element';
import { usePreferredColorSchemeStyle } from '@wordpress/compose';
/**
* Internal dependencies
*/
import styles from './style.scss';
import ColorIndicator from '../color-indicator';
import { colorsUtils } from '../mobile/color-settings/utils';
const ANIMATION_DURATION = 200;
let contentWidth = 0;
let scrollPosition = 0;
let customIndicatorWidth = 0;
function ColorPalette( {
setColor,
activeColor,
isGradientColor,
defaultSettings,
currentSegment,
onCustomPress,
shouldEnableBottomSheetScroll,
shouldShowCustomIndicatorOption = true,
shouldShowCustomLabel = true,
shouldShowCustomVerticalSeparator = true,
customColorIndicatorStyles,
customIndicatorWrapperStyles,
} ) {
const customSwatchGradients = [
'linear-gradient(120deg, rgba(255,0,0,.8) 0%, rgba(255,255,255,1) 70.71%)',
'linear-gradient(240deg, rgba(0,255,0,.8) 0%, rgba(0,255,0,0) 70.71%)',
'linear-gradient(360deg, rgba(0,0,255,.8) 0%, rgba(0,0,255,0) 70.71%)',
];
const isCustomGradientColor = isGradientColor && isSelectedCustom();
const [
shouldShowCustomIndicator,
setShouldShowCustomIndicator,
] = useState(
shouldShowCustomIndicatorOption &&
( ! isGradientSegment || isCustomGradientColor )
);
const scrollViewRef = useRef();
const isIOS = Platform.OS === 'ios';
const isGradientSegment = currentSegment === colorsUtils.segments[ 1 ];
const scale = useRef( new Animated.Value( 1 ) ).current;
const opacity = useRef( new Animated.Value( 1 ) ).current;
const defaultColors = uniq( map( defaultSettings.colors, 'color' ) );
const defaultGradientColors = uniq(
map( defaultSettings.gradients, 'gradient' )
);
const colors = isGradientSegment ? defaultGradientColors : defaultColors;
const customIndicatorColor = isGradientSegment
? activeColor
: customSwatchGradients;
useEffect( () => {
setShouldShowCustomIndicator(
shouldShowCustomIndicatorOption &&
( ! isGradientSegment || isCustomGradientColor )
);
}, [
shouldShowCustomIndicatorOption,
isGradientSegment,
isCustomGradientColor,
] );
const accessibilityHint = isGradientSegment
? __( 'Navigates to customize the gradient' )
: __( 'Navigates to custom color picker' );
const customText = __( 'Custom' );
useEffect( () => {
if ( scrollViewRef.current ) {
if ( isSelectedCustom() ) {
scrollViewRef.current.scrollToEnd();
} else {
scrollViewRef.current.scrollTo( { x: 0, y: 0 } );
}
}
}, [ currentSegment ] );
function isSelectedCustom() {
const isWithinColors =
activeColor && colors && colors.includes( activeColor );
if ( activeColor ) {
if ( isGradientSegment ) {
return isGradientColor && ! isWithinColors;
}
return ! isGradientColor && ! isWithinColors;
}
return false;
}
function isSelected( color ) {
return ! isSelectedCustom() && activeColor === color;
}
function timingAnimation( property, toValue ) {
return Animated.timing( property, {
toValue,
duration: ANIMATION_DURATION,
easing: Easing.ease,
useNativeDriver: true,
} );
}
function performAnimation( color ) {
if ( ! isSelected( color ) ) {
opacity.setValue( 0 );
}
Animated.parallel( [
timingAnimation( scale, 2 ),
timingAnimation( opacity, 1 ),
] ).start( () => {
opacity.setValue( 1 );
scale.setValue( 1 );
} );
}
const scaleInterpolation = scale.interpolate( {
inputRange: [ 1, 1.5, 2 ],
outputRange: [ 1, 0.7, 1 ],
} );
function deselectCustomGradient() {
const { width } = Dimensions.get( 'window' );
const isVisible =
contentWidth - scrollPosition - customIndicatorWidth < width;
if ( isCustomGradientColor ) {
if ( ! isIOS ) {
// Scroll position on Android doesn't adjust automatically when removing the last item from the horizontal list.
// https://github.com/facebook/react-native/issues/27504
// Workaround: Force the scroll when deselecting custom gradient color and when custom indicator is visible on layout.
if (
isCustomGradientColor &&
isVisible &&
scrollViewRef.current
) {
scrollViewRef.current.scrollTo( {
x: scrollPosition - customIndicatorWidth,
} );
}
}
}
}
function onColorPress( color ) {
deselectCustomGradient();
performAnimation( color );
setColor( color );
}
function onContentSizeChange( width ) {
contentWidth = width;
if ( isSelectedCustom() && scrollViewRef.current ) {
scrollViewRef.current.scrollToEnd( { animated: ! isIOS } );
}
}
function onCustomIndicatorLayout( { nativeEvent } ) {
const { width } = nativeEvent.layout;
if ( width !== customIndicatorWidth ) {
customIndicatorWidth = width;
}
}
function onScroll( { nativeEvent } ) {
scrollPosition = nativeEvent.contentOffset.x;
}
const verticalSeparatorStyle = usePreferredColorSchemeStyle(
styles.verticalSeparator,
styles.verticalSeparatorDark
);
const customTextStyle = usePreferredColorSchemeStyle(
[ styles.customText, ! isIOS && styles.customTextAndroid ],
styles.customTextDark
);
const customIndicatorWrapperStyle = [
styles.customIndicatorWrapper,
customIndicatorWrapperStyles,
];
return (
<ScrollView
contentContainerStyle={ styles.contentContainer }
style={ styles.container }
horizontal
showsHorizontalScrollIndicator={ false }
keyboardShouldPersistTaps="always"
disableScrollViewPanResponder
scrollEventThrottle={ 16 }
onScroll={ onScroll }
onContentSizeChange={ onContentSizeChange }
onScrollBeginDrag={ () => shouldEnableBottomSheetScroll( false ) }
onScrollEndDrag={ () => shouldEnableBottomSheetScroll( true ) }
ref={ scrollViewRef }
>
{ colors.map( ( color ) => {
const scaleValue = isSelected( color ) ? scaleInterpolation : 1;
return (
<View key={ `${ color }-${ isSelected( color ) }` }>
<TouchableWithoutFeedback
onPress={ () => onColorPress( color ) }
accessibilityRole={ 'button' }
accessibilityState={ {
selected: isSelected( color ),
} }
accessibilityHint={ color }
>
<Animated.View
style={ {
transform: [
{
scale: scaleValue,
},
],
} }
>
<ColorIndicator
color={ color }
isSelected={ isSelected( color ) }
opacity={ opacity }
style={ [
styles.colorIndicator,
customColorIndicatorStyles,
] }
/>
</Animated.View>
</TouchableWithoutFeedback>
</View>
);
} ) }
{ shouldShowCustomIndicator && (
<View
style={ customIndicatorWrapperStyle }
onLayout={ onCustomIndicatorLayout }
>
{ shouldShowCustomVerticalSeparator && (
<View style={ verticalSeparatorStyle } />
) }
<TouchableWithoutFeedback
onPress={ onCustomPress }
accessibilityRole={ 'button' }
accessibilityState={ {
selected: isSelectedCustom(),
} }
accessibilityHint={ accessibilityHint }
>
<View style={ customIndicatorWrapperStyle }>
<ColorIndicator
withCustomPicker={ ! isGradientSegment }
color={ customIndicatorColor }
isSelected={ isSelectedCustom() }
style={ [
styles.colorIndicator,
customColorIndicatorStyles,
] }
/>
{ shouldShowCustomLabel && (
<Text style={ customTextStyle }>
{ isIOS
? customText
: customText.toUpperCase() }
</Text>
) }
</View>
</TouchableWithoutFeedback>
</View>
) }
</ScrollView>
);
}
export default ColorPalette;