Skip to content
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

Infra/color picker to reanimated #2958

Draft
wants to merge 20 commits into
base: master
Choose a base branch
from
Draft
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
29 changes: 10 additions & 19 deletions demo/src/screens/PlaygroundScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import React, {Component} from 'react';
import {View, Text, Card, TextField, Button} from 'react-native-ui-lib';
import React, {Component, useState} from 'react';
import {View, ColorPicker, Gradient, Button, GradientTypes} from 'react-native-ui-lib';

export default class PlaygroundScreen extends Component {
render() {
return (
<View bg-grey80 flex padding-20>
<View marginT-20>
<TextField migrate placeholder="Placeholder"/>
</View>
<Card height={100} center padding-20>
<Text text50>Playground Screen</Text>
</Card>
<View flex center>
<Button marginV-20 label="Button"/>
</View>
</View>
);
}
}
export default () => {
const [colors, setColors] = useState(['#ddaaaa', '#badddd']);
return (
<View flex bg-grey80 padding-20 gap-80>
<ColorPicker colors={colors} initialColor={colors[0]} onSubmit={color => setColors(prev => [...prev, color])}/>
</View>
);
};
101 changes: 37 additions & 64 deletions src/components/colorPicker/ColorPickerDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import _ from 'lodash';
import React, {useCallback, useEffect, useState} from 'react';
import {LayoutAnimation, StyleSheet, Keyboard, StyleProp, ViewStyle} from 'react-native';
import {useSharedValue} from 'react-native-reanimated';
import tinycolor from 'tinycolor2';
import {Constants, asBaseComponent} from '../../commons/new';
import {Colors} from '../../style';
import {ModalProps} from '../../components/modal';
import Dialog, {DialogProps} from '../../incubator/Dialog';
import {getColorValue, getValidColorString, getTextColor, BORDER_RADIUS, HSLColor} from './ColorPickerPresenter';
import {getTextColor, BORDER_RADIUS} from './ColorPickerPresenter';
import Header from './ColorPickerDialogHeader';
import Preview from './ColorPickerPreview';
import Sliders from './ColorPickerDialogSliders';
import {ColorPickerContextProvider} from './context/ColorPickerContext';

export interface ColorPickerDialogProps extends DialogProps {
/**
Expand Down Expand Up @@ -68,9 +70,7 @@ const ColorPickerDialog = (props: ColorPickerDialogProps) => {
} = props;

const [keyboardHeight, setKeyboardHeight] = useState(KEYBOARD_HEIGHT);
const [color, setColor] = useState(Colors.getHSL(initialColor));
const [text, setText] = useState(getColorValue(initialColor));
const [valid, setValid] = useState(getValidColorString(text).valid);
const color = useSharedValue<tinycolor.ColorFormats.HSLA>(tinycolor(initialColor).toHsl());

const changeHeight = (height: number) => {
setKeyboardHeight(prevKeyboardHeight => {
Expand Down Expand Up @@ -107,13 +107,7 @@ const ColorPickerDialog = (props: ColorPickerDialogProps) => {
}, [keyboardDidShow, keyboardDidHide]);

const resetValues = () => {
const color = Colors.getHSL(initialColor);
const text = getColorValue(initialColor);
const {valid} = getValidColorString(text);

setColor(color);
setText(text);
setValid(valid);
color.value = tinycolor(initialColor).toHsl();
};

const onDismiss = () => {
Expand All @@ -122,8 +116,7 @@ const ColorPickerDialog = (props: ColorPickerDialogProps) => {
};

const onDonePressed = () => {
const {hex} = getValidColorString(text);

const hex = tinycolor(color.value).toHexString();
if (hex) {
props.onSubmit?.(hex, getTextColor(hex));
onDismiss();
Expand All @@ -133,58 +126,38 @@ const ColorPickerDialog = (props: ColorPickerDialogProps) => {
changeHeight(0);
};

const onChangeText = (value: string) => {
applyColor(value);
};

const applyColor = (text: string) => {
const {hex, valid} = getValidColorString(text);

if (hex) {
setColor(Colors.getHSL(hex));
}
setText(text);
setValid(valid);
};

const updateColor = useCallback((value: HSLColor) => {
setColor(value);
const hex = Colors.getHexString(value);
setText(_.toUpper(getColorValue(hex)));
setValid(true);
}, []);
return (
<Dialog
visible={visible} //TODO: pass all Dialog props instead
width="100%"
bottom
centerH
onDismiss={onDismiss}
containerStyle={styles.dialog}
testID={`${testID}.dialog`}
modalProps={MODAL_PROPS}
{...dialogProps}
>
<Header
accessibilityLabels={accessibilityLabels}
valid={valid}
onDonePressed={onDonePressed}
testID={testID}
doneButtonColor={doneButtonColor}
<ColorPickerContextProvider color={color}>
<Dialog
visible={visible} //TODO: pass all Dialog props instead
width="100%"
bottom
centerH
onDismiss={onDismiss}
/>
<Preview
color={color}
text={text}
valid={valid}
accessibilityLabels={accessibilityLabels}
previewInputStyle={previewInputStyle}
testID={testID}
onFocus={onFocus}
onChangeText={onChangeText}
/>
<Sliders keyboardHeight={keyboardHeight} color={color} onSliderValueChange={updateColor} migrate={migrate}/>
</Dialog>
containerStyle={styles.dialog}
testID={`${testID}.dialog`}
modalProps={MODAL_PROPS}
{...dialogProps}
>
<Header
accessibilityLabels={accessibilityLabels}
onDonePressed={onDonePressed}
testID={testID}
doneButtonColor={doneButtonColor}
onDismiss={onDismiss}
/>
<Preview
accessibilityLabels={accessibilityLabels}
previewInputStyle={previewInputStyle}
testID={testID}
onFocus={onFocus}
onChangeText={(value: string) => {
color.value = tinycolor(value).toHsl();
}}
/>
<Sliders keyboardHeight={keyboardHeight} migrate/>
</Dialog>
</ColorPickerContextProvider>
);
};

Expand Down
20 changes: 16 additions & 4 deletions src/components/colorPicker/ColorPickerDialogHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import _ from 'lodash';
import React from 'react';
import React, {useContext, useState} from 'react';
import {StyleSheet} from 'react-native';

import View from '../view';
Expand All @@ -8,15 +8,27 @@ import Assets from '../../assets';
import {Colors} from '../../style';
import {ColorPickerDialogProps} from './ColorPickerDialog';
import {BORDER_RADIUS} from './ColorPickerPresenter';
import {ColorPickerContext} from './context/ColorPickerContext';
import {runOnJS, useAnimatedReaction} from 'react-native-reanimated';

type HeaderProps = Pick<ColorPickerDialogProps, 'doneButtonColor' | 'accessibilityLabels' | 'testID'> & {
valid: boolean;
onDismiss: () => void;
onDonePressed: () => void;
onDonePressed: (hex: string) => void;
};

const Header = (props: HeaderProps) => {
const {onDismiss, accessibilityLabels, testID, doneButtonColor, valid, onDonePressed} = props;
const {onDismiss, accessibilityLabels, testID, doneButtonColor, onDonePressed} = props;
const [valid, setValid] = useState(false);
const colorPickerContext = useContext(ColorPickerContext);

useAnimatedReaction(() => {
return colorPickerContext?.isValid.value;
},
(currentValidity, prevValidity) => {
if (currentValidity !== prevValidity) {
runOnJS(setValid)(!!currentValidity);
}
});

return (
<View row spread bg-$backgroundDefault paddingH-20 style={styles.header}>
Expand Down
17 changes: 10 additions & 7 deletions src/components/colorPicker/ColorPickerDialogSliders.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
import React from 'react';
import React, {useContext} from 'react';
import {StyleSheet} from 'react-native';
import {Colors} from '../../style';
import ColorSliderGroup from '../slider/ColorSliderGroup';
import {HSLColor} from './ColorPickerPresenter';
import {ColorPickerDialogProps} from './ColorPickerDialog';
import {ColorPickerContext} from './context/ColorPickerContext';

type SlidersProps = Pick<ColorPickerDialogProps, 'migrate'> & {
keyboardHeight: number;
color: HSLColor;
onSliderValueChange: (value: HSLColor) => void;
};

const Sliders = (props: SlidersProps) => {
const {keyboardHeight, color, migrate, onSliderValueChange} = props;
const colorValue = color.a === 0 ? Colors.getHSL(Colors.$backgroundInverted) : color;

const {keyboardHeight, migrate} = props;
const colorPickerContext = useContext(ColorPickerContext);
const colorValue =
!colorPickerContext || colorPickerContext.statefulColor.a === 0
? Colors.getHSL(Colors.$backgroundInverted)
: colorPickerContext.statefulColor;
console.log(`Nitzan - sliders colorValue`, colorValue);
return (
<ColorSliderGroup<HSLColor>
key={Colors.getHexString(colorValue)}
initialColor={colorValue}
containerStyle={[styles.sliderGroup, {height: keyboardHeight}]}
sliderContainerStyle={styles.slider}
showLabels
labelsStyle={styles.label}
accessible={false}
migrate={migrate}
onValueChange={onSliderValueChange}
/>
);
};
Expand Down
31 changes: 29 additions & 2 deletions src/components/colorPicker/ColorPickerPresenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,53 @@ export function getColorValue(color?: string) {
}
return color.replace('#', '');
}

export function isTransparent(color?: string) {
'worklet';
return color && color.toUpperCase() === 'transparent'.toUpperCase();
}
export function getHexColor(text: string) {
if (!Colors.isTransparent(text)) {
'worklet';
if (!isTransparent(text)) {
const trimmed = text.replace(/\s+/g, '');
const hex = `#${trimmed}`;
return hex;
}
return text;
}

export function isValidHex(string: string) {
'worklet';
return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(string);
}

export function getValidColorString(text?: string) {
'worklet';
if (text) {
const hex = getHexColor(text);

if (Colors.isValidHex(hex)) {
if (isValidHex(hex)) {
return {hex, valid: true};
}
}
return {undefined, valid: false};
}

export function hslToHex(hsl: {h: number; s: number; l: number}) {
'worklet';
let {h, s, l} = hsl;
l /= 100;
const a = (s * Math.min(l, 1 - l)) / 100;
const f = (n: number) => {
const k = (n + h / 30) % 12;
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return Math.round(255 * color)
.toString(16)
.padStart(2, '0'); // convert to Hex and prefix "0" if needed
};
return `${f(0)}${f(8)}${f(4)}`;
}

export function getHexString(color: HSLColor) {
return _.toUpper(Colors.getHexString(color));
}
Expand Down
Loading