diff --git a/ColorPickerPOC/ColorPickerPOC.js b/ColorPickerPOC/ColorPickerPOC.js index d8b6d5931c..ce7c26ee1c 100644 --- a/ColorPickerPOC/ColorPickerPOC.js +++ b/ColorPickerPOC/ColorPickerPOC.js @@ -12,7 +12,7 @@ import Skinnable from '../Skinnable'; import TabLayout, {Tab} from '../TabLayout'; import ColorPickerGrid from './ColorPickerGrid'; -import ColorPickerSlider from './ColorPickerSlider'; +import ColorPickerSlider, {ColorPickerSliderHSL} from './ColorPickerSlider'; // eslint-disable-line no-unused-vars import ColorPickerSpectrum from './ColorPickerSpectrum'; import {generateOppositeColor} from './utils'; @@ -201,6 +201,7 @@ const ColorPickerPOCBase = ({color = '#eb4034', colors = [], css, onChangeColor,
+ {/* */}
diff --git a/ColorPickerPOC/ColorPickerSlider.js b/ColorPickerPOC/ColorPickerSlider.js index 8c628c0c36..7328dd3177 100644 --- a/ColorPickerPOC/ColorPickerSlider.js +++ b/ColorPickerPOC/ColorPickerSlider.js @@ -1,90 +1,281 @@ import {Cell, Row} from '@enact/ui/Layout'; import PropTypes from 'prop-types'; -import {useCallback} from 'react'; +import {useCallback, useState} from 'react'; import Slider from '../Slider'; -import {generateOppositeColor, hexToRGB, rgbObjectToHex} from './utils'; +import {generateOppositeColor, hexToHSL, hexToRGB, hslToHex, hslToRGBString, rgbObjectToHex} from './utils'; import componentCss from './ColorPickerSlider.module.less'; +const hueGradient = (saturation, lightness) => { + return `linear-gradient(to right, + hsla(0, ${saturation}%, ${lightness}%, 1), + hsla(10, ${saturation}%, ${lightness}%, 1), + hsla(20, ${saturation}%, ${lightness}%, 1), + hsla(30, ${saturation}%, ${lightness}%, 1), + hsla(40, ${saturation}%, ${lightness}%, 1), + hsla(50, ${saturation}%, ${lightness}%, 1), + hsla(60, ${saturation}%, ${lightness}%, 1), + hsla(70, ${saturation}%, ${lightness}%, 1), + hsla(80, ${saturation}%, ${lightness}%, 1), + hsla(90, ${saturation}%, ${lightness}%, 1), + hsla(100, ${saturation}%, ${lightness}%, 1), + hsla(110, ${saturation}%, ${lightness}%, 1), + hsla(120, ${saturation}%, ${lightness}%, 1), + hsla(130, ${saturation}%, ${lightness}%, 1), + hsla(140, ${saturation}%, ${lightness}%, 1), + hsla(150, ${saturation}%, ${lightness}%, 1), + hsla(160, ${saturation}%, ${lightness}%, 1), + hsla(170, ${saturation}%, ${lightness}%, 1), + hsla(180, ${saturation}%, ${lightness}%, 1), + hsla(190, ${saturation}%, ${lightness}%, 1), + hsla(200, ${saturation}%, ${lightness}%, 1), + hsla(210, ${saturation}%, ${lightness}%, 1), + hsla(220, ${saturation}%, ${lightness}%, 1), + hsla(230, ${saturation}%, ${lightness}%, 1), + hsla(240, ${saturation}%, ${lightness}%, 1), + hsla(250, ${saturation}%, ${lightness}%, 1), + hsla(260, ${saturation}%, ${lightness}%, 1), + hsla(270, ${saturation}%, ${lightness}%, 1), + hsla(280, ${saturation}%, ${lightness}%, 1), + hsla(290, ${saturation}%, ${lightness}%, 1), + hsla(300, ${saturation}%, ${lightness}%, 1), + hsla(310, ${saturation}%, ${lightness}%, 1), + hsla(320, ${saturation}%, ${lightness}%, 1), + hsla(330, ${saturation}%, ${lightness}%, 1), + hsla(340, ${saturation}%, ${lightness}%, 1), + hsla(350, ${saturation}%, ${lightness}%, 1), + hsla(360, ${saturation}%, ${lightness}%, 1))`; +}; + +const lightnessGradient = (hue, saturation) => { + return `linear-gradient(to right, + hsla(${hue}, ${saturation}%, 0%, 1), + hsla(${hue}, ${saturation}%, 20%, 1), + hsla(${hue}, ${saturation}%, 40%, 1), + hsla(${hue}, ${saturation}%, 60%, 1), + hsla(${hue}, ${saturation}%, 80%, 1), + hsla(${hue}, ${saturation}%, 100%, 1))`; +}; + const ColorPickerSlider = ({selectedColor, selectedColorHandler, ...props}) => { const {red, green, blue} = hexToRGB(selectedColor); + const [localRed, setLocalRed] = useState(red); + const [localGreen, setLocalGreen] = useState(green); + const [localBlue, setLocalBlue] = useState(blue); const changeValueRed = useCallback((ev) => { - selectedColorHandler(rgbObjectToHex({red: ev.value, green, blue})); - }, [blue, green, selectedColorHandler]); + setLocalRed(ev.value); + }, []); const changeValueGreen = useCallback((ev) => { - selectedColorHandler(rgbObjectToHex({red, green: ev.value, blue})); - }, [blue, red, selectedColorHandler]); + setLocalGreen(ev.value); + }, []); const changeValueBlue = useCallback((ev) => { - selectedColorHandler(rgbObjectToHex({red, green, blue: ev.value})); - }, [green, red, selectedColorHandler]); + setLocalBlue(ev.value); + }, []); + + const changeSelectedColor = useCallback(() => { + selectedColorHandler(rgbObjectToHex({red: localRed, green: localGreen, blue: localBlue})); + }, [localBlue, localGreen, localRed, selectedColorHandler]); return (
Red - + {red} + >{localRed} Green - + - {green} + {localGreen} Blue - + - {blue} + {localBlue} + + +
+ ); +}; + +const ColorPickerSliderHSL = ({selectedColor, selectedColorHandler, ...props}) => { + const {h, s, l} = hexToHSL(selectedColor); + + const [hue, setHue] = useState(h); + const [saturation, setSaturation] = useState(s); + const [lightness, setLightness] = useState(l); + + const changeValueHue = useCallback((ev) => { + setHue(ev.value); + }, []); + + const changeValueSaturation = useCallback((ev) => { + setSaturation(ev.value); + }, []); + + const changeValueLightness = useCallback((ev) => { + setLightness(ev.value); + }, []); + + const changeSelectedColor = useCallback(() => { + selectedColorHandler(hslToHex({h: hue, s: saturation, l: lightness})); + }, [hue, saturation, lightness, selectedColorHandler]); + + return ( +
+ + Hue + + + + + {hue} + + + + Saturation + + + + + {saturation}% + + + + Lightness + + + + + {lightness}%
@@ -96,4 +287,13 @@ ColorPickerSlider.propTypes = { selectedColorHandler: PropTypes.func }; +ColorPickerSliderHSL.propTypes = { + selectedColor: PropTypes.string, + selectedColorHandler: PropTypes.func +}; + +export { + ColorPickerSlider, + ColorPickerSliderHSL +}; export default ColorPickerSlider; diff --git a/ColorPickerPOC/ColorPickerSlider.module.less b/ColorPickerPOC/ColorPickerSlider.module.less index 446a836f62..2ba045213b 100644 --- a/ColorPickerPOC/ColorPickerSlider.module.less +++ b/ColorPickerPOC/ColorPickerSlider.module.less @@ -4,6 +4,7 @@ .labelText { font-size: 42px; + margin-bottom: 12px; } .outputText { @@ -13,16 +14,38 @@ color: #444444; display: grid; justify-content: end; - padding-right: 24px; + margin-left: 30px; + padding-right: 21px; +} + +.outputTextPercent { + align-content: center; + background-color: #eeeeee; + border-radius: 12px; + color: #444444; + display: grid; + justify-content: end; + margin-left: 30px; } .slider { - --sand-focus-bg-color-rgb: transparent; --sand-progress-bg-color-rgb: transparent; - --sand-progress-slider-color: transparent; border-radius: 99px; height: 42px; - margin-left: 0; + margin-left: 18px; + margin-right: 18px; + + .knob { + &::before { + border-radius: 99px; + height: 120px; + width: 120px; + } + } +} + +.sliderCell { + border-radius: 99px; } .sliderContainer { diff --git a/ColorPickerPOC/utils.js b/ColorPickerPOC/utils.js index 5892b544e2..97163dfb1b 100644 --- a/ColorPickerPOC/utils.js +++ b/ColorPickerPOC/utils.js @@ -35,6 +35,47 @@ const generateOppositeColor = (hexColor) => { return luminance > 128 ? '#101720CC' : '#FFFFFFCC'; }; +const hexToHSL = (hexColor) => { + // Convert hex to RGB first + let r = 0, g = 0, b = 0; + r = parseInt(hexColor.slice(1, 3), 16); + g = parseInt(hexColor.slice(3, 5), 16); + b = parseInt(hexColor.slice(5), 16); + + // Then convert RGB to HSL + r /= 255; + g /= 255; + b /= 255; + let cmin = Math.min(r, g, b), + cmax = Math.max(r, g, b), + delta = cmax - cmin, + h, s, l; + + if (delta === 0) { + h = 0; + } else if (cmax === r) { + h = ((g - b) / delta) % 6; + } else if (cmax === g) { + h = (b - r) / delta + 2; + } else { + h = (r - g) / delta + 4; + } + + h = Math.round(h * 60); + + if (h < 0) { + h += 360; + } + + l = (cmax + cmin) / 2; + s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); + + s = +(s * 100).toFixed(1); + l = +(l * 100).toFixed(1); + + return {h: Math.round(h), s: Math.round(s), l: Math.round(l)}; +}; + const hexToRGB = (hexColor) => { let internalColor = hexColor.replace('#', '').split(''); @@ -45,6 +86,83 @@ const hexToRGB = (hexColor) => { }; }; +const hslToHex = ({h, s, l}) => { + s /= 100; + l /= 100; + + let c = (1 - Math.abs(2 * l - 1)) * s, + x = c * (1 - Math.abs((h / 60) % 2 - 1)), + m = l - c / 2, + r = 0, + g = 0, + b = 0; + + if (0 <= h && h < 60) { + r = c; g = x; b = 0; + } else if (60 <= h && h < 120) { + r = x; g = c; b = 0; + } else if (120 <= h && h < 180) { + r = 0; g = c; b = x; + } else if (180 <= h && h < 240) { + r = 0; g = x; b = c; + } else if (240 <= h && h < 300) { + r = x; g = 0; b = c; + } else if (300 <= h && h < 360) { + r = c; g = 0; b = x; + } + // Having obtained RGB, convert channels to hex + r = Math.round((r + m) * 255).toString(16); + g = Math.round((g + m) * 255).toString(16); + b = Math.round((b + m) * 255).toString(16); + + // Prepend 0s, if necessary + if (r.length === 1) { + r = "0" + r; + } + + if (g.length === 1) { + g = "0" + g; + } + + if (b.length === 1) { + b = "0" + b; + } + + return "#" + r + g + b; +}; + +const hslToRGBString = ({h, s, l}) => { + s /= 100; + l /= 100; + + let c = (1 - Math.abs(2 * l - 1)) * s, + x = c * (1 - Math.abs((h / 60) % 2 - 1)), + m = l - c / 2, + r = 0, + g = 0, + b = 0; + + if (0 <= h && h < 60) { + r = c; g = x; b = 0; + } else if (60 <= h && h < 120) { + r = x; g = c; b = 0; + } else if (120 <= h && h < 180) { + r = 0; g = c; b = x; + } else if (180 <= h && h < 240) { + r = 0; g = x; b = c; + } else if (240 <= h && h < 300) { + r = x; g = 0; b = c; + } else if (300 <= h && h < 360) { + r = c; g = 0; b = x; + } + + // Having obtained RGB, convert channels to hex + r = Math.round((r + m) * 255); + g = Math.round((g + m) * 255); + b = Math.round((b + m) * 255); + return r + ',' + g + ',' + b; +}; + const rgbObjectToHex = (rgbColor) => { let {red, green, blue} = rgbColor; red = red < 16 ? `0${red.toString(16)}` : red.toString(16); @@ -57,7 +175,10 @@ const rgbObjectToHex = (rgbColor) => { export { generateOppositeColor, getHexColorFromGradient, + hexToHSL, hexToRGB, + hslToHex, + hslToRGBString, rgbObjectToHex, rgbStringToHex };