reanimated-color-picker
Version:
A Pure JavaScript Color Picker for React Native
260 lines (256 loc) • 6.09 kB
JavaScript
import React, { useCallback } from 'react';
import { Image, View } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, { Easing, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming } from 'react-native-reanimated';
import colorKit from '../../colorKit/index';
import usePickerContext from '../../AppContext';
import { styles } from '../../styles';
import { getStyle, isRtl } from '../../utils';
const animationOptions = {
duration: 300,
easing: Easing.elastic(0.8),
};
/** - This is a grid of 120 colors, arranged in 12 columns and 10 rows of squares. */
export function Panel5({ gestures = [], style = {}, selectionStyle = {} }) {
const { hueValue, saturationValue, brightnessValue, onGestureChange, onGestureEnd } = usePickerContext();
const borderRadius = getStyle(style, 'borderRadius') ?? 0;
const squareSize = useSharedValue(0),
posX = useSharedValue(0),
posY = useSharedValue(0),
adaptiveColor = useSharedValue('#000');
const setAdaptiveColor = color1 => {
'worklet';
const color = adaptiveColor.value === '#ffffff' ? '#000000' : '#ffffff';
const contrast = colorKit.runOnUI().contrastRatio(color1, adaptiveColor.value);
adaptiveColor.value = contrast < 4.5 ? color : adaptiveColor.value;
};
// Calculate the position of the selected square on color change.
// Since color conversion is not 100% accurate, we need to find the closest color in the grid.
useDerivedValue(() => {
const hsvColor = {
h: hueValue.value,
s: saturationValue.value,
v: brightnessValue.value,
};
for (let y = 0; y < gridColors.length; y++) {
for (let x = 0; x < gridColors[y].length; x++) {
const gridColor = gridColors[y][x];
const areColorsEqual = colorKit.runOnUI().areColorsEqual(gridColor, hsvColor, 5);
if (!areColorsEqual) continue;
setAdaptiveColor(gridColor);
posX.value = withTiming(x, animationOptions);
posY.value = withTiming(y, animationOptions);
break;
}
}
}, [hueValue, saturationValue, brightnessValue, posX, posY]);
const selectedStyle = useAnimatedStyle(() => {
const x = posX.value * squareSize.value;
const y = posY.value * squareSize.value;
return {
width: squareSize.value,
height: squareSize.value,
top: y,
left: isRtl ? undefined : x,
right: isRtl ? x : undefined,
borderColor: adaptiveColor.value,
};
}, [squareSize, adaptiveColor, posX, posY]);
const tap = Gesture.Tap().onBegin(({ x, y }) => {
var _gridColors$row;
if (!squareSize.value) return;
const row = Math.floor(y / squareSize.value);
const column = Math.floor(x / squareSize.value);
const color = (_gridColors$row = gridColors[row]) === null || _gridColors$row === void 0 ? void 0 : _gridColors$row[column];
if (!color) return;
const { h, s, v } = colorKit.runOnUI().HSV(color).object(false);
hueValue.value = h;
saturationValue.value = s;
brightnessValue.value = v;
setAdaptiveColor(color);
onGestureChange();
onGestureEnd();
});
const composed = Gesture.Simultaneous(tap, ...gestures);
const onLayout = useCallback(({ nativeEvent: { layout } }) => {
squareSize.value = withTiming(layout.width / 12 || layout.height / 10, {
duration: 100,
});
}, []);
return /*#__PURE__*/ React.createElement(
GestureDetector,
{
gesture: composed,
},
/*#__PURE__*/ React.createElement(
View,
{
collapsable: false,
onLayout: onLayout,
style: [
style,
{
position: 'relative',
borderWidth: 0,
padding: 0,
aspectRatio: 1.2,
},
],
},
/*#__PURE__*/ React.createElement(Image, {
source: require('../../assets/grid.png'),
style: {
borderRadius,
width: '100%',
height: '100%',
},
resizeMode: 'stretch',
}),
/*#__PURE__*/ React.createElement(Animated.View, {
style: [styles.selected, selectionStyle, selectedStyle],
}),
),
);
}
const gridColors = [
[
'#FFFFFF',
'#EBEBEB',
'#D6D6D6',
'#C2C2C2',
'#ADADAD',
'#999999',
'#858585',
'#707070',
'#5C5C5C',
'#474747',
'#333333',
'#000000',
],
[
'#00374A',
'#011D57',
'#11053B',
'#2E063D',
'#3C071B',
'#5C0701',
'#5A1C00',
'#583300',
'#563D00',
'#666100',
'#4F5504',
'#263E0F',
],
[
'#004D65',
'#012F7B',
'#1A0A52',
'#450D59',
'#551029',
'#831100',
'#7B2900',
'#7A4A00',
'#785800',
'#8D8602',
'#6F760A',
'#38571A',
],
[
'#016E8F',
'#0042A9',
'#2C0977',
'#61187C',
'#791A3D',
'#B51A00',
'#AD3E00',
'#A96800',
'#A67B01',
'#C4BC00',
'#9BA50E',
'#4E7A27',
],
[
'#008CB4',
'#0056D6',
'#371A94',
'#7A219E',
'#99244F',
'#E22400',
'#DA5100',
'#D38301',
'#D19D01',
'#F5EC00',
'#C3D117',
'#669D34',
],
[
'#00A1D8',
'#0061FD',
'#4D22B2',
'#982ABC',
'#B92D5D',
'#FF4015',
'#FF6A00',
'#FFAB01',
'#FCC700',
'#FEFB41',
'#D9EC37',
'#76BB40',
],
[
'#01C7FC',
'#3A87FD',
'#5E30EB',
'#BE38F3',
'#E63B7A',
'#FE6250',
'#FE8648',
'#FEB43F',
'#FECB3E',
'#FFF76B',
'#E4EF65',
'#96D35F',
],
[
'#52D6FC',
'#74A7FF',
'#864FFD',
'#D357FE',
'#EE719E',
'#FF8C82',
'#FEA57D',
'#FEC777',
'#FED977',
'#FFF994',
'#EAF28F',
'#B1DD8B',
],
[
'#93E3FC',
'#A7C6FF',
'#B18CFE',
'#E292FE',
'#F4A4C0',
'#FFB5AF',
'#FFC5AB',
'#FED9A8',
'#FDE4A8',
'#FFFBB9',
'#F1F7B7',
'#CDE8B5',
],
[
'#CBF0FF',
'#D2E2FE',
'#D8C9FE',
'#EFCAFE',
'#F9D3E0',
'#FFDAD8',
'#FFE2D6',
'#FEECD4',
'#FEF1D5',
'#FDFBDD',
'#F6FADB',
'#DEEED4',
],
];