UNPKG

@storybook/addon-ondevice-controls

Version:

Display storybook controls on your device.

165 lines (164 loc) 7.02 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { I18nManager, Image, StyleSheet, Text, View, } from 'react-native'; import tinycolor from 'tinycolor2'; import SliderWrapper from '../SliderWrapper'; import { createPanResponder } from './utils'; const getHsv = (color) => tinycolor(color).toHsv(); export function HoloColorPicker({ defaultColor, oldColor, onColorChange, style, }) { const [color, setColor] = useState(() => getHsv(defaultColor)); const [pickerSize, setPickerSize] = useState(0); const colorRef = useRef(color); const pickerSizeRef = useRef(pickerSize); const pickerRef = useRef(null); const pickerPageOffsetRef = useRef({ x: 0, y: 0 }); useEffect(() => { const nextColor = getHsv(defaultColor); colorRef.current = nextColor; // eslint-disable-next-line react-hooks/set-state-in-effect setColor(nextColor); }, [defaultColor]); const updateColor = useCallback((nextColor) => { colorRef.current = nextColor; setColor(nextColor); onColorChange(nextColor); }, [onColorChange]); const handleHueChange = useCallback(({ x, y }) => { const currentPickerSize = pickerSizeRef.current; if (!currentPickerSize) { return; } const relativeX = x - pickerPageOffsetRef.current.x; const relativeY = y - pickerPageOffsetRef.current.y; const h = computeHue(relativeX, relativeY, currentPickerSize); updateColor({ ...colorRef.current, h }); }, [updateColor]); const pickerResponder = useMemo(() => // eslint-disable-next-line react-hooks/refs createPanResponder({ onStart: handleHueChange, onMove: handleHueChange, }), [handleHueChange]); const onLayout = (event) => { const { width, height } = event.nativeEvent.layout; const nextPickerSize = Math.min(width, height); pickerSizeRef.current = nextPickerSize; setPickerSize(nextPickerSize); }; const onPickerLayout = () => { requestAnimationFrame(() => { pickerRef.current?.measure((_x, _y, _width, _height, pageX, pageY) => { pickerPageOffsetRef.current = { x: pageX, y: pageY }; }); }); }; const selectedColor = tinycolor(color).toHexString(); const indicatorColor = tinycolor({ h: color.h, s: 1, v: 1 }).toHexString(); const computed = makeComputedStyles({ pickerSize, selectedColor, indicatorColor, oldColor, angle: hueToRad(color.h), isRTL: I18nManager.isRTL, }); return (_jsxs(View, { style: style, children: [_jsx(View, { onLayout: onLayout, style: styles.pickerContainer, children: !pickerSize ? null : (_jsxs(View, { children: [_jsxs(View, { ref: pickerRef, onLayout: onPickerLayout, ...pickerResponder.panHandlers, style: computed.picker, collapsable: false, children: [_jsx(Image, { source: require('./resources/color-circle.png'), resizeMode: "contain", style: styles.pickerImage }), _jsx(View, { style: [styles.pickerIndicator, computed.pickerIndicator] })] }), oldColor ? (_jsxs(_Fragment, { children: [_jsx(View, { style: [styles.selectedPreview, computed.selectedPreview] }), _jsx(View, { style: [styles.originalPreview, computed.originalPreview] })] })) : (_jsx(View, { style: [styles.selectedFullPreview, computed.selectedFullPreview] }))] })) }), _jsxs(View, { children: [_jsx(Text, { style: styles.sliderLabel, children: "Saturation" }), _jsx(SliderWrapper, { value: color.s, onValueChange: (s) => updateColor({ ...colorRef.current, s }) }), _jsx(Text, { style: styles.sliderLabel, children: "Lightness" }), _jsx(SliderWrapper, { value: color.v, onValueChange: (v) => updateColor({ ...colorRef.current, v }) })] })] })); } const computeHue = (x, y, pickerSize) => { const center = pickerSize / 2; const dx = x - center; const dy = y - center; const rad = Math.atan2(dx, dy) + Math.PI + Math.PI / 2; return ((rad * 180) / Math.PI) % 360; }; const hueToRad = (deg) => { const rad = (deg * Math.PI) / 180; return rad - Math.PI - Math.PI / 2; }; const makeComputedStyles = ({ indicatorColor, selectedColor, oldColor, angle, pickerSize, isRTL, }) => { const summarySize = 0.5 * pickerSize; const indicatorSize = (42 / 510) * pickerSize; const pickerPadding = indicatorSize / 3; const indicatorRadius = pickerSize / 2 - indicatorSize / 2 - pickerPadding; const center = pickerSize / 2; const dx = Math.cos(angle) * indicatorRadius; const dy = Math.sin(angle) * indicatorRadius; return { picker: { padding: pickerPadding, width: pickerSize, height: pickerSize, }, pickerIndicator: { top: center + dx - indicatorSize / 2, [isRTL ? 'right' : 'left']: center + dy - indicatorSize / 2, width: indicatorSize, height: indicatorSize, borderRadius: indicatorSize / 2, backgroundColor: indicatorColor, }, selectedPreview: { width: summarySize / 2, height: summarySize, top: pickerSize / 2 - summarySize / 2, left: Math.floor(pickerSize / 2), borderTopRightRadius: summarySize / 2, borderBottomRightRadius: summarySize / 2, backgroundColor: selectedColor, }, originalPreview: { width: Math.ceil(summarySize / 2), height: summarySize, top: pickerSize / 2 - summarySize / 2, left: pickerSize / 2 - summarySize / 2, borderTopLeftRadius: summarySize / 2, borderBottomLeftRadius: summarySize / 2, backgroundColor: oldColor, }, selectedFullPreview: { width: summarySize, height: summarySize, top: pickerSize / 2 - summarySize / 2, left: pickerSize / 2 - summarySize / 2, borderRadius: summarySize / 2, backgroundColor: selectedColor, }, }; }; const styles = StyleSheet.create({ pickerContainer: { flex: 1, alignItems: 'center', justifyContent: 'center', }, pickerImage: { flex: 1, width: undefined, height: undefined, }, pickerIndicator: { position: 'absolute', pointerEvents: 'none', boxShadow: '3px 3px 4px 0px rgba(0, 0, 0, 0.3)', elevation: 5, }, selectedPreview: { position: 'absolute', borderLeftWidth: 0, pointerEvents: 'none', }, originalPreview: { position: 'absolute', borderRightWidth: 0, pointerEvents: 'none', }, selectedFullPreview: { position: 'absolute', pointerEvents: 'none', }, sliderLabel: { paddingStart: 4, color: '#859499', fontSize: 12, }, });