From 81061b4be1dd535c86a8861288c56df5c5452fe7 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp <122601449+ion-andrusciac-lgp@users.noreply.github.com> Date: Thu, 1 Aug 2024 14:51:49 +0300 Subject: [PATCH 01/24] WRQ-30515: Created component skeleton (#1650) * Created component skeleton for ColorPickerPOC * Added propTypes * Fixed lint warnings --- ColorPickerPOC/ColorPickerPOC.js | 158 ++++++++++++++++++ ColorPickerPOC/ColorPickerPOC.module.less | 32 ++++ ColorPickerPOC/package.json | 3 + .../sampler/stories/default/ColorPickerPOC.js | 22 +++ 4 files changed, 215 insertions(+) create mode 100644 ColorPickerPOC/ColorPickerPOC.js create mode 100644 ColorPickerPOC/ColorPickerPOC.module.less create mode 100644 ColorPickerPOC/package.json create mode 100644 samples/sampler/stories/default/ColorPickerPOC.js diff --git a/ColorPickerPOC/ColorPickerPOC.js b/ColorPickerPOC/ColorPickerPOC.js new file mode 100644 index 0000000000..183ad35337 --- /dev/null +++ b/ColorPickerPOC/ColorPickerPOC.js @@ -0,0 +1,158 @@ +import kind from '@enact/core/kind'; +import Spottable from '@enact/spotlight/Spottable'; +import {Cell, Row} from '@enact/ui/Layout'; +import Toggleable from '@enact/ui/Toggleable'; +import ri from '@enact/ui/resolution'; +import PropTypes from 'prop-types'; +import compose from 'ramda/src/compose'; +import {useCallback, useState} from 'react'; + +import {Button, ButtonBase} from '../Button'; +import Popup from '../Popup'; +import Skinnable from '../Skinnable'; +import TabLayout, {Tab} from '../TabLayout'; + +import componentsCss from './ColorPickerPOC.module.less'; + +const SpottableButton = Spottable(ButtonBase); + +const FavoriteColors = ({colorHandler, colors = [], css, selectedColor = '#3455eb'}) => { + const [currentColor, setCurrentColor] = useState(selectedColor); + const [favoriteColors, setFavoriteColors] = useState(colors); + + const onSelectFavoriteColor = useCallback((ev) => { + const color = ev.target.offsetParent.id; + setCurrentColor(color); + + colorHandler({currentColor: color, favoriteColors}); + }, [colorHandler, favoriteColors]); + + const addNewFavoriteColor = useCallback(() => { + if (favoriteColors.length > 6) favoriteColors.shift(); + + setFavoriteColors(prevState => { + const colorsState = [...prevState, selectedColor]; + colorHandler({currentColor, favoriteColors: colorsState}); + + return colorsState; + }); + }, [colorHandler, currentColor, favoriteColors, selectedColor]); + + const onAddNewFavoriteColor = useCallback(() => { + if (!document.startViewTransition) { + addNewFavoriteColor(); + return; + } + + document.startViewTransition(() => { + addNewFavoriteColor(); + }); + }, [addNewFavoriteColor]); + + return ( +
+ + + {favoriteColors.length >= 4 &&
+ ); +}; + +FavoriteColors.propTypes = { + colorHandler: PropTypes.func, + colors: PropTypes.array, + css: PropTypes.object, + selectedColor: PropTypes.string +}; + +const ColorPickerPOCBase = kind({ + name: 'ColorPickerPOC', + + functional: true, + + propTypes: { + color: PropTypes.string, + colors: PropTypes.array, + css: PropTypes.object, + favoriteColors: PropTypes.array, + onChangeColor: PropTypes.func, + onToggleColorPicker: PropTypes.func, + open: PropTypes.bool + }, + + handlers: { + handleOpenPopup: (ev, {onToggleColorPicker}) => { + onToggleColorPicker(); + } + }, + + styles: { + css: componentsCss + }, + + render: ({color, open, colors, css, onChangeColor}) => { + + return ( + + + + + +
+ Grid +
+
+ +
+ Spectrum +
+
+ +
+ Sliders +
+
+
+
+ + + +
+
+ ); + } +}); + +const ColorPickerPOCDecorator = compose( + Skinnable, + Toggleable({prop: 'colorPickerOpen', toggle: 'onToggleColorPicker'}) +); + +const ColorPickerPOC = ColorPickerPOCDecorator(ColorPickerPOCBase); + +export default ColorPickerPOC; +export { + ColorPickerPOC, + ColorPickerPOCBase, + ColorPickerPOCDecorator +}; diff --git a/ColorPickerPOC/ColorPickerPOC.module.less b/ColorPickerPOC/ColorPickerPOC.module.less new file mode 100644 index 0000000000..2d3c2de9b4 --- /dev/null +++ b/ColorPickerPOC/ColorPickerPOC.module.less @@ -0,0 +1,32 @@ +@import '../styles/mixins.less'; + +.colorPicker { + height: 800px; + width: 800px; +} + +.content { + margin-inline: auto; +} + +.presetColorsRow { + text-align: end; + + .currentColor { + border: 2px solid rgba(204, 204, 204, 0.9); + border-radius: 24px; + height: 240px; + width: 93%; + } + + .presetColor { + border: 2px solid rgba(204, 204, 204, 0.9); + border-radius: 50%; + transition: transform .2s; + + .focus({ + box-shadow: 0 0 0 18px rgba(204, 204, 204, 0.6) inset; + transform: scale(1.1); + }); + } +} diff --git a/ColorPickerPOC/package.json b/ColorPickerPOC/package.json new file mode 100644 index 0000000000..39b80f324f --- /dev/null +++ b/ColorPickerPOC/package.json @@ -0,0 +1,3 @@ +{ + "main": "ColorPickerPOC.js" +} diff --git a/samples/sampler/stories/default/ColorPickerPOC.js b/samples/sampler/stories/default/ColorPickerPOC.js new file mode 100644 index 0000000000..519cca94ec --- /dev/null +++ b/samples/sampler/stories/default/ColorPickerPOC.js @@ -0,0 +1,22 @@ +import {action} from '@enact/storybook-utils/addons/actions'; + +import ColorPicker from '../../../../ColorPickerPOC/ColorPickerPOC'; + +ColorPicker.displayName = 'ColorPicker'; + +const colors = ['#eb4034', '#32a852', '#3455eb']; + +export default { + title: 'Sandstone/ColorPicker', + component: 'ColorPicker' +}; + +export const _ColorPicker = () => ( + +); + +_ColorPicker.displayName = 'ColorPicker'; From 86997102155b36941e0c4f629dd2207733bfa7b0 Mon Sep 17 00:00:00 2001 From: Daniel Stoian <63335068+daniel-stoian-lgp@users.noreply.github.com> Date: Thu, 1 Aug 2024 17:43:04 +0300 Subject: [PATCH 02/24] WRQ-30516: [POC] Added Grid Color Picker (#1651) * added color picker grid component * added logic for new color picker grid component * fixed story name * adjueste component size * fixed color array values * updated cell size * unified colorpickerpoc and colorpickergrid * removed unwanted story * lint fixes * renamed util function --- ColorPickerPOC/ColorPickerGrid.js | 141 ++++++++++++++++++ ColorPickerPOC/ColorPickerGrid.module.less | 27 ++++ ColorPickerPOC/ColorPickerPOC.js | 137 ++++++++--------- ColorPickerPOC/utils.js | 17 +++ .../sampler/stories/default/ColorPickerPOC.js | 3 +- 5 files changed, 249 insertions(+), 76 deletions(-) create mode 100644 ColorPickerPOC/ColorPickerGrid.js create mode 100644 ColorPickerPOC/ColorPickerGrid.module.less create mode 100644 ColorPickerPOC/utils.js diff --git a/ColorPickerPOC/ColorPickerGrid.js b/ColorPickerPOC/ColorPickerGrid.js new file mode 100644 index 0000000000..5067a30165 --- /dev/null +++ b/ColorPickerPOC/ColorPickerGrid.js @@ -0,0 +1,141 @@ +/** + * Sandstone component to allow the user to choose a color from a grid. + * + * @example + * + * + * @module sandstone/ColorPickerGrid + * @exports ColorPickerGrid + * @exports ColorPickerGridBase + * @exports ColorPickerGridDecorator + * @private + */ +import Spottable from '@enact/spotlight/Spottable'; +import classnames from 'classnames'; +import PropTypes from 'prop-types'; +import compose from 'ramda/src/compose'; +import {useCallback} from 'react'; + +import Skinnable from '../Skinnable'; + +import {rgbStringToHex} from './utils'; + +import componentCss from './ColorPickerGrid.module.less'; + +const SpottableDiv = Spottable('div'); + +const colors = [ + ['#ffffff', '#00374a', '#004d65', '#016e8f', '#008cb4', '#00a1d8', '#01c7fc', '#52d6fc', '#93e3fd', '#cbf0ff'], + ['#ebebeb', '#011d57', '#012f7b', '#0042a9', '#0056d6', '#0061fe', '#3a87fe', '#74a7ff', '#a7c6ff', '#d3e2ff'], + ['#d6d6d6', '#11053b', '#1a0a52', '#2c0977', '#371a94', '#4d22b2', '#5e30eb', '#864ffe', '#b18cfe', '#d9c9fe'], + ['#c2c2c2', '#2e063d', '#450d59', '#61187c', '#7a219e', '#982abc', '#be38f3', '#d357fe', '#e292fe', '#efcaff'], + ['#adadad', '#3c071b', '#551029', '#791a3d', '#99244f', '#b92d5d', '#e63b7a', '#ee719e', '#f4a4c0', '#f9d3e0'], + ['#999999', '#5c0701', '#831100', '#b51a00', '#e22400', '#ff4015', '#ff6250', '#ff8c82', '#ffb5af', '#ffdbd8'], + ['#858585', '#5a1c00', '#7b2900', '#ad3e00', '#da5100', '#ff6a00', '#ff8648', '#ffa57d', '#ffc5ab', '#ffe2d6'], + ['#707070', '#583300', '#7a4a00', '#a96800', '#d38301', '#ffab01', '#feb43f', '#ffc777', '#ffd9a8', '#ffecd4'], + ['#5c5c5c', '#563d00', '#785800', '#a67b01', '#d19d01', '#fdc700', '#fecb3e', '#ffd977', '#fee4a8', '#fff2d5'], + ['#474747', '#666100', '#8d8602', '#c4bc00', '#f5ec00', '#fefb41', '#fff76b', '#fff994', '#fffbb9', '#fefcdd'], + ['#333333', '#4f5504', '#6f760a', '#9ba50e', '#c3d117', '#d9ec37', '#e4ef65', '#eaf28f', '#f2f7b7', '#f7fadb'], + ['#000000', '#263e0f', '#38571a', '#4e7a27', '#669d34', '#76bb40', '#96d35f', '#b1dd8b', '#cde8b5', '#dfeed4'] +]; + +/** + * The color picker base component which sets-up the component's structure. + * + * This component is most often not used directly but may be composed within another component as it + * is within {@link sandstone/ColorPickerGrid|ColorPickerGrid}. + * + * @class ColorPickerGridBase + * @memberof sandstone/ColorPickerGrid + * @ui + * @private + */ + +const ColorPickerGridBase = (props) => { + const {className, selectedColorHandler, ...rest} = props; + + const handleClick = useCallback((e) => { + selectedColorHandler(rgbStringToHex(e.target.style.backgroundColor)); + }, [selectedColorHandler]); + + return ( +
+ { + colors.map((row, rowIndex) => { + return ( +
+ { + colors[rowIndex].map((color, colorIndex) => { + return ; + }) + } +
+ ); + }) + } +
+ ); +}; + +ColorPickerGridBase.propTypes = {/** @lends sandstone/ColorPickerGrid.ColorPickerGridBase.prototype */ + /** + * Customizes the component by mapping the supplied collection of CSS class names to the + * corresponding internal elements and states of this component. + * + * + * @type {Object} + * @public + */ + css: PropTypes.object, + + /** + * Applies the `disabled` class. + * + * When `true`, the color picker is shown as disabled. + * + * @type {Boolean} + * @default false + * @public + */ + disabled: PropTypes.bool, + + /** + * A function to run when the current color changes. + * + * @type {Function} + * @public + */ + selectedColorHandler: PropTypes.func +}; + +/** + * Applies Sandstone specific behaviors to {@link sandstone/ColorPicker.ColorPickerBase|ColorPicker} components. + * + * @hoc + * @memberof sandstone/ColorPickerGrid + * @mixes sandstone/Skinnable.Skinnable + * @private + */ +const ColorPickerGridDecorator = compose( + Skinnable +); + +/** + * A color picker component, ready to use in Sandstone applications. + * + * @class ColorPickerGrid + * @memberof sandstone/ColorPickerGrid + * @extends sandstone/ColorPickerGrid.ColorPickerGridBase + * @mixes sandstone/ColorPickerGrid.ColorPickerGridDecorator + * @ui + * @private + */ +const ColorPickerGrid = ColorPickerGridDecorator(ColorPickerGridBase); + +export default ColorPickerGrid; +export { + ColorPickerGrid, + ColorPickerGridBase, + ColorPickerGridDecorator +}; diff --git a/ColorPickerPOC/ColorPickerGrid.module.less b/ColorPickerPOC/ColorPickerGrid.module.less new file mode 100644 index 0000000000..8911bcd270 --- /dev/null +++ b/ColorPickerPOC/ColorPickerGrid.module.less @@ -0,0 +1,27 @@ +// ColorPicker.module.less +// +@import '../styles/colors.less'; +@import '../styles/mixins.less'; +@import '../styles/variables.less'; + +.colorPicker { + .colorsColumn { + float: left; + } + + .colorBlock { + border-color: transparent; + border-style: solid; + border-width: 9px; + height: 48px; + width: 48px; + + .focus({ + border-color: red; + // transform: scale(1.5); + // transition: all 0.375s cubic-bezier(0.4, 0, 0.2, 1); + }) + } +} + + diff --git a/ColorPickerPOC/ColorPickerPOC.js b/ColorPickerPOC/ColorPickerPOC.js index 183ad35337..43ddff184f 100644 --- a/ColorPickerPOC/ColorPickerPOC.js +++ b/ColorPickerPOC/ColorPickerPOC.js @@ -1,42 +1,40 @@ -import kind from '@enact/core/kind'; import Spottable from '@enact/spotlight/Spottable'; import {Cell, Row} from '@enact/ui/Layout'; -import Toggleable from '@enact/ui/Toggleable'; import ri from '@enact/ui/resolution'; import PropTypes from 'prop-types'; import compose from 'ramda/src/compose'; -import {useCallback, useState} from 'react'; +import {useCallback, useEffect, useState} from 'react'; import {Button, ButtonBase} from '../Button'; import Popup from '../Popup'; import Skinnable from '../Skinnable'; import TabLayout, {Tab} from '../TabLayout'; +import ColorPickerGrid from './ColorPickerGrid'; + import componentsCss from './ColorPickerPOC.module.less'; const SpottableButton = Spottable(ButtonBase); -const FavoriteColors = ({colorHandler, colors = [], css, selectedColor = '#3455eb'}) => { - const [currentColor, setCurrentColor] = useState(selectedColor); +const FavoriteColors = ({colorHandler, colors = [], selectedColor = '#3455eb', selectedColorHandler}) => { const [favoriteColors, setFavoriteColors] = useState(colors); const onSelectFavoriteColor = useCallback((ev) => { const color = ev.target.offsetParent.id; - setCurrentColor(color); - + selectedColorHandler(color); colorHandler({currentColor: color, favoriteColors}); - }, [colorHandler, favoriteColors]); + }, [colorHandler, favoriteColors, selectedColorHandler]); const addNewFavoriteColor = useCallback(() => { if (favoriteColors.length > 6) favoriteColors.shift(); setFavoriteColors(prevState => { const colorsState = [...prevState, selectedColor]; - colorHandler({currentColor, favoriteColors: colorsState}); + colorHandler({selectedColor, favoriteColors: colorsState}); return colorsState; }); - }, [colorHandler, currentColor, favoriteColors, selectedColor]); + }, [colorHandler, favoriteColors, selectedColor]); const onAddNewFavoriteColor = useCallback(() => { if (!document.startViewTransition) { @@ -51,27 +49,27 @@ const FavoriteColors = ({colorHandler, colors = [], css, selectedColor = '#3455e return (
- + - {favoriteColors.length >= 4 &&
@@ -82,70 +80,61 @@ FavoriteColors.propTypes = { colorHandler: PropTypes.func, colors: PropTypes.array, css: PropTypes.object, - selectedColor: PropTypes.string + selectedColor: PropTypes.string, + selectedColorHandler: PropTypes.func }; -const ColorPickerPOCBase = kind({ - name: 'ColorPickerPOC', - functional: true, +const ColorPickerPOCBase = ({color, colors = [], css, onChangeColor, open, ...rest}) => { + const [selectedColor, setSelectedColor] = useState(color); - propTypes: { - color: PropTypes.string, - colors: PropTypes.array, - css: PropTypes.object, - favoriteColors: PropTypes.array, - onChangeColor: PropTypes.func, - onToggleColorPicker: PropTypes.func, - open: PropTypes.bool - }, - - handlers: { - handleOpenPopup: (ev, {onToggleColorPicker}) => { - onToggleColorPicker(); + useEffect(() => { + if (selectedColor) { + onChangeColor({currentColor: selectedColor}); } - }, - - styles: { - css: componentsCss - }, - - render: ({color, open, colors, css, onChangeColor}) => { - - return ( - - - - - -
- Grid -
-
- -
- Spectrum -
-
- -
- Sliders -
-
-
-
- - - -
-
- ); - } -}); + }, [onChangeColor, selectedColor]); + + return ( + + + + + +
+ +
+
+ +
+ Spectrum +
+
+ +
+ Sliders +
+
+
+
+ + + +
+
+ ); +}; + +ColorPickerPOCBase.displayName = 'ColorPickerPOC'; +ColorPickerPOCBase.propTypes = { + color: PropTypes.string, + colors: PropTypes.array, + css: PropTypes.object, + onChangeColor: PropTypes.func, + open: PropTypes.bool +}; const ColorPickerPOCDecorator = compose( - Skinnable, - Toggleable({prop: 'colorPickerOpen', toggle: 'onToggleColorPicker'}) + Skinnable ); const ColorPickerPOC = ColorPickerPOCDecorator(ColorPickerPOCBase); diff --git a/ColorPickerPOC/utils.js b/ColorPickerPOC/utils.js new file mode 100644 index 0000000000..d7d843a4ea --- /dev/null +++ b/ColorPickerPOC/utils.js @@ -0,0 +1,17 @@ +function rgbStringToHex (rgbString) { + if (rgbString.indexOf("rgb(") !== 0) return false; + + let a = rgbString.substring(4).split(","); // an array ("204","204","153)") + if (a.length < 3) return false; // still something wrong with the string + + for (let i = 0; i < 3; i++) { + a[i] = parseInt(a[i]).toString(16); // parse integer, convert integer to hex string + a[i] = ((a[i].length === 1) ? "0" : "") + a[i].substring(0, 2); + // pad single digit hex numbers with a leading 0 + } + return ("#" + a[0] + a[1] + a[2]).toUpperCase(); +} + +export { + rgbStringToHex +}; diff --git a/samples/sampler/stories/default/ColorPickerPOC.js b/samples/sampler/stories/default/ColorPickerPOC.js index 519cca94ec..7be45636d1 100644 --- a/samples/sampler/stories/default/ColorPickerPOC.js +++ b/samples/sampler/stories/default/ColorPickerPOC.js @@ -1,7 +1,6 @@ +import ColorPicker from '@enact/sandstone/ColorPickerPOC'; import {action} from '@enact/storybook-utils/addons/actions'; -import ColorPicker from '../../../../ColorPickerPOC/ColorPickerPOC'; - ColorPicker.displayName = 'ColorPicker'; const colors = ['#eb4034', '#32a852', '#3455eb']; From 4b68b699ddb95be3e59c5d73933c07b4192158c1 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp <122601449+ion-andrusciac-lgp@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:08:23 +0300 Subject: [PATCH 03/24] WRQ-30515: Created component skeleton (#1653) * Created component skeleton for ColorPickerPOC * Added propTypes * Fixed lint warnings * Added option to remove favorite color on long press * Applied design changes for the Favorite Colors section * Added shake animation for delete action --- ColorPickerPOC/ColorPickerPOC.js | 144 ++++++++++++++---- ColorPickerPOC/ColorPickerPOC.module.less | 60 ++++++-- ColorPickerPOC/utils.js | 16 +- .../sampler/stories/default/ColorPickerPOC.js | 1 + 4 files changed, 173 insertions(+), 48 deletions(-) diff --git a/ColorPickerPOC/ColorPickerPOC.js b/ColorPickerPOC/ColorPickerPOC.js index 43ddff184f..e6f9151f75 100644 --- a/ColorPickerPOC/ColorPickerPOC.js +++ b/ColorPickerPOC/ColorPickerPOC.js @@ -1,35 +1,36 @@ import Spottable from '@enact/spotlight/Spottable'; -import {Cell, Row} from '@enact/ui/Layout'; +import {Cell, Column, Row} from '@enact/ui/Layout'; import ri from '@enact/ui/resolution'; import PropTypes from 'prop-types'; import compose from 'ramda/src/compose'; -import {useCallback, useEffect, useState} from 'react'; +import {useCallback, useEffect, useRef, useState} from 'react'; -import {Button, ButtonBase} from '../Button'; +import {ButtonBase} from '../Button'; +import Icon from '../Icon'; import Popup from '../Popup'; import Skinnable from '../Skinnable'; import TabLayout, {Tab} from '../TabLayout'; import ColorPickerGrid from './ColorPickerGrid'; +import {generateOppositeColor} from './utils'; import componentsCss from './ColorPickerPOC.module.less'; const SpottableButton = Spottable(ButtonBase); const FavoriteColors = ({colorHandler, colors = [], selectedColor = '#3455eb', selectedColorHandler}) => { + const [clickEnabled, setClickEnabled] = useState(true); + const [editEnabled, setEditEnabled] = useState(false); const [favoriteColors, setFavoriteColors] = useState(colors); - const onSelectFavoriteColor = useCallback((ev) => { - const color = ev.target.offsetParent.id; - selectedColorHandler(color); - colorHandler({currentColor: color, favoriteColors}); - }, [colorHandler, favoriteColors, selectedColorHandler]); + const shakeEffectRef = useRef(null); + const timerRef = useRef(null); const addNewFavoriteColor = useCallback(() => { - if (favoriteColors.length > 6) favoriteColors.shift(); - + if (favoriteColors.includes(selectedColor)) return; setFavoriteColors(prevState => { const colorsState = [...prevState, selectedColor]; + if (colorsState.length > 8) colorsState.shift(); colorHandler({selectedColor, favoriteColors: colorsState}); return colorsState; @@ -37,41 +38,114 @@ const FavoriteColors = ({colorHandler, colors = [], selectedColor = '#3455eb', s }, [colorHandler, favoriteColors, selectedColor]); const onAddNewFavoriteColor = useCallback(() => { - if (!document.startViewTransition) { - addNewFavoriteColor(); + if (editEnabled) { + setEditEnabled(false); return; } + addNewFavoriteColor(); + }, [addNewFavoriteColor, editEnabled]); - document.startViewTransition(() => { - addNewFavoriteColor(); - }); - }, [addNewFavoriteColor]); + const onSelectFavoriteColor = useCallback((ev) => { + if (!clickEnabled) return; + const targetId = ev.target.offsetParent.id || ev.target.id; + const [buttonColor, buttonIndex] = targetId.split('-'); + if (editEnabled && clickEnabled) { + setFavoriteColors(prevState => + prevState.filter((stateColor, index) => { + return !(stateColor === buttonColor && index === Number(buttonIndex)); + })); + return; + } + selectedColorHandler(buttonColor); + colorHandler({currentColor: buttonColor, favoriteColors}); + }, [clickEnabled, colorHandler, editEnabled, favoriteColors, selectedColorHandler]); + + const onMouseDown = useCallback((ev) => { + if (editEnabled) return; + const target = ev.target.id ? ev.target : ev.target.offsetParent; + shakeEffectRef.current = setTimeout(() => { + target.classList.add(componentsCss.shakeFavoriteColor); + }, 300); + timerRef.current = setTimeout(() => { + setEditEnabled(true); + setClickEnabled(false); + }, 1000); + }, [editEnabled]); + + const onMouseUp = useCallback((ev) => { + const target = ev.target.id ? ev.target : ev.target.offsetParent; + target.classList.remove(componentsCss.shakeFavoriteColor); + clearTimeout(shakeEffectRef.current); + clearTimeout(timerRef.current); + setTimeout(() => { + setClickEnabled(true); + }, 100); + }, []); return (
- + - {favoriteColors.length >= 4 &&
); }; @@ -85,7 +159,7 @@ FavoriteColors.propTypes = { }; -const ColorPickerPOCBase = ({color, colors = [], css, onChangeColor, open, ...rest}) => { +const ColorPickerPOCBase = ({color = '#eb4034', colors = [], css, onChangeColor, open, ...rest}) => { const [selectedColor, setSelectedColor] = useState(color); useEffect(() => { @@ -97,7 +171,7 @@ const ColorPickerPOCBase = ({color, colors = [], css, onChangeColor, open, ...re return ( - +
@@ -116,8 +190,16 @@ const ColorPickerPOCBase = ({color, colors = [], css, onChangeColor, open, ...re - - + + + + diff --git a/ColorPickerPOC/ColorPickerPOC.module.less b/ColorPickerPOC/ColorPickerPOC.module.less index 2d3c2de9b4..52cc3514e0 100644 --- a/ColorPickerPOC/ColorPickerPOC.module.less +++ b/ColorPickerPOC/ColorPickerPOC.module.less @@ -1,32 +1,60 @@ @import '../styles/mixins.less'; .colorPicker { - height: 800px; - width: 800px; + display: flex; + justify-content: center; + min-width: 800px; } -.content { - margin-inline: auto; -} - -.presetColorsRow { - text-align: end; +.currentColor { + border-radius: 24px; + border: 9px solid; + height: 240px; + transition: transform .2s; + width: 100%; - .currentColor { - border: 2px solid rgba(204, 204, 204, 0.9); - border-radius: 24px; - height: 240px; - width: 93%; + .currentColorIcon { + transition: transform .2s; } - .presetColor { - border: 2px solid rgba(204, 204, 204, 0.9); + .focus({ + box-shadow: 0 0 12px 6px inset; + + .currentColorIcon { + transform: scale(1.2); + } + }); +} + +.favoriteColorsRow { + text-align: center; + + .favoriteColor { border-radius: 50%; + border: 6px solid; transition: transform .2s; + .deleteButton { + position: absolute; + z-index: -2; + } + .focus({ - box-shadow: 0 0 0 18px rgba(204, 204, 204, 0.6) inset; + box-shadow: 0 0 12px 3px inset; transform: scale(1.1); }); } + + .shakeFavoriteColor { + animation: shakeColor 0.2s; + animation-iteration-count: infinite; + } +} + +@keyframes shakeColor { + 0% { transform: translateX(0) } + 25% { transform: translateX(3px) } + 50% { transform: translateX(-3px) } + 75% { transform: translateX(3px) } + 100% { transform: translateX(0) } } diff --git a/ColorPickerPOC/utils.js b/ColorPickerPOC/utils.js index d7d843a4ea..34acb0acac 100644 --- a/ColorPickerPOC/utils.js +++ b/ColorPickerPOC/utils.js @@ -12,6 +12,20 @@ function rgbStringToHex (rgbString) { return ("#" + a[0] + a[1] + a[2]).toUpperCase(); } +const generateOppositeColor = (hexColor) => { + hexColor = hexColor.replace('#', ''); + + const bigint = parseInt(hexColor, 16); + const r = (bigint >> 16) & 255; + const g = (bigint >> 8) & 255; + const b = bigint & 255; + + const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b; + + return luminance > 128 ? '#101720CC' : '#FFFFFFCC'; +}; + export { - rgbStringToHex + rgbStringToHex, + generateOppositeColor }; diff --git a/samples/sampler/stories/default/ColorPickerPOC.js b/samples/sampler/stories/default/ColorPickerPOC.js index 7be45636d1..9addacaec8 100644 --- a/samples/sampler/stories/default/ColorPickerPOC.js +++ b/samples/sampler/stories/default/ColorPickerPOC.js @@ -12,6 +12,7 @@ export default { export const _ColorPicker = () => ( Date: Tue, 6 Aug 2024 11:54:42 +0300 Subject: [PATCH 04/24] WRQ-30516: Optimized styling for grid color picker (#1654) * added opposite color for focus on colorpickergrid * added box shadow for focused color --- ColorPickerPOC/ColorPickerGrid.js | 4 ++-- ColorPickerPOC/ColorPickerGrid.module.less | 5 +++-- ColorPickerPOC/utils.js | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/ColorPickerPOC/ColorPickerGrid.js b/ColorPickerPOC/ColorPickerGrid.js index 5067a30165..1ebc63db3e 100644 --- a/ColorPickerPOC/ColorPickerGrid.js +++ b/ColorPickerPOC/ColorPickerGrid.js @@ -19,7 +19,7 @@ import {useCallback} from 'react'; import Skinnable from '../Skinnable'; -import {rgbStringToHex} from './utils'; +import {generateOppositeColor, rgbStringToHex} from './utils'; import componentCss from './ColorPickerGrid.module.less'; @@ -67,7 +67,7 @@ const ColorPickerGridBase = (props) => {
{ colors[rowIndex].map((color, colorIndex) => { - return ; + return ; }) }
diff --git a/ColorPickerPOC/ColorPickerGrid.module.less b/ColorPickerPOC/ColorPickerGrid.module.less index 8911bcd270..f1631128d5 100644 --- a/ColorPickerPOC/ColorPickerGrid.module.less +++ b/ColorPickerPOC/ColorPickerGrid.module.less @@ -17,8 +17,9 @@ width: 48px; .focus({ - border-color: red; - // transform: scale(1.5); + border-color: var(--sand-colorpicker-grid-focus-border-color); + box-shadow: 0 0 12px 3px inset; + // transform: scale(1.1); // transition: all 0.375s cubic-bezier(0.4, 0, 0.2, 1); }) } diff --git a/ColorPickerPOC/utils.js b/ColorPickerPOC/utils.js index 34acb0acac..739487f7e9 100644 --- a/ColorPickerPOC/utils.js +++ b/ColorPickerPOC/utils.js @@ -26,6 +26,6 @@ const generateOppositeColor = (hexColor) => { }; export { - rgbStringToHex, - generateOppositeColor + generateOppositeColor, + rgbStringToHex }; From d3343aa366c31ddc6ce529030188cb505ce4589c Mon Sep 17 00:00:00 2001 From: alexandru-morariu-lgp <84004128+alexandrumorariu@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:28:24 +0300 Subject: [PATCH 05/24] WRQ-30517: Slider type panel for the WebOS ColorPicker. (#1655) * Added ColorPickerSlider functionality to ColorPickerPOC. * Small fix for ColorPickerSlider. * Transparent knob with border. * Merge with main branch. * Lint fix. * Review fix. * Review fix. --- ColorPickerPOC/ColorPickerPOC.js | 3 +- ColorPickerPOC/ColorPickerPOC.module.less | 1 + ColorPickerPOC/ColorPickerSlider.js | 99 ++++++++++++++++++++ ColorPickerPOC/ColorPickerSlider.module.less | 30 ++++++ ColorPickerPOC/utils.js | 21 +++++ styles/colors.less | 2 +- 6 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 ColorPickerPOC/ColorPickerSlider.js create mode 100644 ColorPickerPOC/ColorPickerSlider.module.less diff --git a/ColorPickerPOC/ColorPickerPOC.js b/ColorPickerPOC/ColorPickerPOC.js index e6f9151f75..a5e6fb0eab 100644 --- a/ColorPickerPOC/ColorPickerPOC.js +++ b/ColorPickerPOC/ColorPickerPOC.js @@ -12,6 +12,7 @@ import Skinnable from '../Skinnable'; import TabLayout, {Tab} from '../TabLayout'; import ColorPickerGrid from './ColorPickerGrid'; +import ColorPickerSlider from './ColorPickerSlider'; import {generateOppositeColor} from './utils'; import componentsCss from './ColorPickerPOC.module.less'; @@ -185,7 +186,7 @@ const ColorPickerPOCBase = ({color = '#eb4034', colors = [], css, onChangeColor,
- Sliders +
diff --git a/ColorPickerPOC/ColorPickerPOC.module.less b/ColorPickerPOC/ColorPickerPOC.module.less index 52cc3514e0..9a3923adcf 100644 --- a/ColorPickerPOC/ColorPickerPOC.module.less +++ b/ColorPickerPOC/ColorPickerPOC.module.less @@ -2,6 +2,7 @@ .colorPicker { display: flex; + height: 660px; justify-content: center; min-width: 800px; } diff --git a/ColorPickerPOC/ColorPickerSlider.js b/ColorPickerPOC/ColorPickerSlider.js new file mode 100644 index 0000000000..8c628c0c36 --- /dev/null +++ b/ColorPickerPOC/ColorPickerSlider.js @@ -0,0 +1,99 @@ +import {Cell, Row} from '@enact/ui/Layout'; +import PropTypes from 'prop-types'; +import {useCallback} from 'react'; + +import Slider from '../Slider'; + +import {generateOppositeColor, hexToRGB, rgbObjectToHex} from './utils'; + +import componentCss from './ColorPickerSlider.module.less'; + +const ColorPickerSlider = ({selectedColor, selectedColorHandler, ...props}) => { + const {red, green, blue} = hexToRGB(selectedColor); + + const changeValueRed = useCallback((ev) => { + selectedColorHandler(rgbObjectToHex({red: ev.value, green, blue})); + }, [blue, green, selectedColorHandler]); + + const changeValueGreen = useCallback((ev) => { + selectedColorHandler(rgbObjectToHex({red, green: ev.value, blue})); + }, [blue, red, selectedColorHandler]); + + const changeValueBlue = useCallback((ev) => { + selectedColorHandler(rgbObjectToHex({red, green, blue: ev.value})); + }, [green, red, selectedColorHandler]); + + return ( +
+ + Red + + + + + {red} + + + + Green + + + + + {green} + + + + Blue + + + + + {blue} + + +
+ ); +}; + +ColorPickerSlider.propTypes = { + selectedColor: PropTypes.string, + selectedColorHandler: PropTypes.func +}; + +export default ColorPickerSlider; diff --git a/ColorPickerPOC/ColorPickerSlider.module.less b/ColorPickerPOC/ColorPickerSlider.module.less new file mode 100644 index 0000000000..446a836f62 --- /dev/null +++ b/ColorPickerPOC/ColorPickerSlider.module.less @@ -0,0 +1,30 @@ +.cellElement { + margin-top: 21px; +} + +.labelText { + font-size: 42px; +} + +.outputText { + align-content: center; + background-color: #eeeeee; + border-radius: 12px; + color: #444444; + display: grid; + justify-content: end; + padding-right: 24px; +} + +.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; +} + +.sliderContainer { + width: 900px; +} diff --git a/ColorPickerPOC/utils.js b/ColorPickerPOC/utils.js index 739487f7e9..4722d7f833 100644 --- a/ColorPickerPOC/utils.js +++ b/ColorPickerPOC/utils.js @@ -25,7 +25,28 @@ const generateOppositeColor = (hexColor) => { return luminance > 128 ? '#101720CC' : '#FFFFFFCC'; }; +const hexToRGB = (hexColor) => { + let internalColor = hexColor.replace('#', '').split(''); + + return { + red: parseInt(internalColor[0] + internalColor[1], 16), + green: parseInt(internalColor[2] + internalColor[3], 16), + blue: parseInt(internalColor[4] + internalColor[5], 16) + }; +}; + +const rgbObjectToHex = (rgbColor) => { + let {red, green, blue} = rgbColor; + red = red < 16 ? `0${red.toString(16)}` : red.toString(16); + green = green < 16 ? `0${green.toString(16)}` : green.toString(16); + blue = blue < 16 ? `0${blue.toString(16)}` : blue.toString(16); + + return `#${red}${green}${blue}`; +}; + export { generateOppositeColor, + hexToRGB, + rgbObjectToHex, rgbStringToHex }; diff --git a/styles/colors.less b/styles/colors.less index 5198b0de7c..bf6e118436 100644 --- a/styles/colors.less +++ b/styles/colors.less @@ -99,6 +99,7 @@ @sand-formcheckboxitem-focus-text-color: var(--sand-formcheckboxitem-focus-text-color, @sand-focus-text-color); @sand-item-disabled-focus-bg-color: var(--sand-item-disabled-focus-bg-color, #e6e6e6); @sand-keyguide-bg-color-rgb: var(--sand-keyguide-bg-color-rgb, 55, 58, 65); // #373a41 +@sand-slider-knob-border-color: var(--sand-slider-knob-border-color, transparent); @sand-slider-disabled-knob-bg-color: var(--sand-slider-disabled-knob-bg-color, #666666); @sand-iconitem-border-color: var(--sand-iconitem-border-color, #707070); @@ -389,7 +390,6 @@ @sand-slider-load-bg-color: @sand-progressbar-load-bg-color; @sand-slider-fill-bg-color: @sand-progress-slider-color; @sand-slider-knob-bg-color: @sand-slider-fill-bg-color; -@sand-slider-knob-border-color: transparent; @sand-slider-focus-bar-bg-color: @sand-slider-bar-bg-color; @sand-slider-focus-load-bg-color: @sand-slider-load-bg-color; @sand-slider-focus-fill-bg-color: @sand-focus-bg-color; From 183ca1d0aaf8f1fae02dcae6cf1201e755da769b Mon Sep 17 00:00:00 2001 From: adrian-cocoara-lgp <63335823+adrian-cocoara-lgp@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:31:40 +0300 Subject: [PATCH 06/24] WRQ-30519: [POC] webOS ColorPicker - spectrum color picker (#1656) * *WIP* implement spectrum color picker * added positionPointer, wrapped ColorPicker in Spottable, code cleanup * fixed spottable div * *WIP* added 5-way navigation to indicator * color selection via 5-way, code cleanup, fix lint * code review fixes --------- Co-authored-by: Daniel Stoian --- ColorPickerPOC/ColorPickerPOC.js | 3 +- ColorPickerPOC/ColorPickerSpectrum.js | 140 ++++++++++++++++++ .../ColorPickerSpectrum.module.less | 22 +++ ColorPickerPOC/SpectrumIndicator.js | 114 ++++++++++++++ ColorPickerPOC/utils.js | 11 ++ 5 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 ColorPickerPOC/ColorPickerSpectrum.js create mode 100644 ColorPickerPOC/ColorPickerSpectrum.module.less create mode 100644 ColorPickerPOC/SpectrumIndicator.js diff --git a/ColorPickerPOC/ColorPickerPOC.js b/ColorPickerPOC/ColorPickerPOC.js index a5e6fb0eab..ca73776a59 100644 --- a/ColorPickerPOC/ColorPickerPOC.js +++ b/ColorPickerPOC/ColorPickerPOC.js @@ -13,6 +13,7 @@ import TabLayout, {Tab} from '../TabLayout'; import ColorPickerGrid from './ColorPickerGrid'; import ColorPickerSlider from './ColorPickerSlider'; +import ColorPickerSpectrum from './ColorPickerSpectrum'; import {generateOppositeColor} from './utils'; import componentsCss from './ColorPickerPOC.module.less'; @@ -181,7 +182,7 @@ const ColorPickerPOCBase = ({color = '#eb4034', colors = [], css, onChangeColor,
- Spectrum +
diff --git a/ColorPickerPOC/ColorPickerSpectrum.js b/ColorPickerPOC/ColorPickerSpectrum.js new file mode 100644 index 0000000000..b3c877cfe2 --- /dev/null +++ b/ColorPickerPOC/ColorPickerSpectrum.js @@ -0,0 +1,140 @@ +import PropTypes from 'prop-types'; +import {useCallback, useEffect, useRef, useState} from 'react'; + +import {getHexColorFromGradient} from './utils'; +import SpectrumIndicator from './SpectrumIndicator'; + +import css from './ColorPickerSpectrum.module.less'; + +const SpectrumColorPicker = (props) => { + const {selectedColor, selectedColorHandler} = props; + const canvasRef = useRef(null); + const [indicatorX, setIndicatorX] = useState(0); + const [indicatorY, setIndicatorY] = useState(0); + const [isDragging, setIsDragging] = useState(false); + const [indicatorBgColor, setIndicatorBgColor] = useState('transparent'); + const [isIndicatorActive, setIsIndicatorActive] = useState(false); + + useEffect(() => { + const canvas = canvasRef.current; + const ctx = canvas.getContext('2d'); + + const createColorGradient = (canvasElement, context) => { + for (let i = 0; i < canvasElement.width; i++) { + const luminosity = 1 - (i / canvasElement.width); // Max luminosity on the left, min luminosity on the right + const gradient = context.createLinearGradient(0, 0, 0, canvasElement.height); + gradient.addColorStop(0, `hsl(0, 100%, ${luminosity * 100}%)`); // Red + gradient.addColorStop(1 / 6, `hsl(30, 100%, ${luminosity * 100}%)`); // Orange + gradient.addColorStop(2 / 6, `hsl(60, 100%, ${luminosity * 100}%)`); // Yellow + gradient.addColorStop(3 / 6, `hsl(120, 100%, ${luminosity * 100}%)`); // Green + gradient.addColorStop(4 / 6, `hsl(180, 100%, ${luminosity * 100}%)`); // Blue + gradient.addColorStop(5 / 6, `hsl(240, 100%, ${luminosity * 100}%)`); // Indigo + gradient.addColorStop(1, `hsl(269, 100%, ${luminosity * 100}%)`); // Violet + context.fillStyle = gradient; + context.fillRect(i, 0, 1, canvasElement.height); + } + }; + createColorGradient(canvas, ctx); + + // Position the indicator on the canvas based on the selected color + const positionIndicator = () => { + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + for (let x = 0; x < canvas.width; x++) { + for (let y = 0; y < canvas.height; y++) { + const pixelIndex = (y * canvas.width * 4) + (x * 4); + const pixelColor = `#${imageData.data[pixelIndex].toString(16).padStart(2, '0')}${imageData.data[pixelIndex + 1].toString(16).padStart(2, '0')}${imageData.data[pixelIndex + 2].toString(16).padStart(2, '0')}`; + if (pixelColor === selectedColor) { + setIndicatorX(x); + setIndicatorY(y); + return; + } else { + // if the color is not found, position the indicator at the origin(0, 0) of the canvas + setIndicatorX(0); + setIndicatorY(0); + } + } + } + }; + positionIndicator(); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + const handleCanvasPointerDown = useCallback((e) => { + const canvas = canvasRef.current; + const rect = canvas.getBoundingClientRect(); + let x = e.clientX - rect.left; + let y = e.clientY - rect.top; + + setIndicatorX(x); + setIndicatorY(y); + setIsDragging(true); + + const hexColor = getHexColorFromGradient(canvasRef, indicatorX, indicatorY); + setIndicatorBgColor(hexColor); + setIsIndicatorActive(false); + }, [canvasRef, indicatorX, indicatorY, setIndicatorX, setIndicatorY, setIsDragging]); + + const handleCanvasPointerLeave = useCallback(() => { + setIsDragging(false); + + const hexColor = getHexColorFromGradient(canvasRef, indicatorX, indicatorY); + selectedColorHandler(hexColor); + setIndicatorBgColor(hexColor); + }, [canvasRef, indicatorX, indicatorY, setIsDragging, selectedColorHandler]); + + const handleCanvasPointerMove = useCallback((e) => { + if (isDragging) { + const canvas = canvasRef.current; + const rect = canvas.getBoundingClientRect(); + let x = e.clientX - rect.left; + let y = e.clientY - rect.top; + + setIndicatorX(x); + setIndicatorY(y); + + const hexColor = getHexColorFromGradient(canvasRef, indicatorX, indicatorY); + setIndicatorBgColor(hexColor); + } + }, [canvasRef, indicatorX, indicatorY, isDragging]); + + const handleCanvasPointerUp = useCallback(() => { + const hexColor = getHexColorFromGradient(canvasRef, indicatorX, indicatorY); + selectedColorHandler(hexColor); + setIndicatorBgColor(hexColor); + setIsDragging(false); + }, [canvasRef, indicatorX, indicatorY, setIsDragging, selectedColorHandler]); + + return ( +
+ + +
+ ); +}; + +SpectrumColorPicker.displayName = 'SpectrumColorPicker'; +SpectrumColorPicker.propTypes = { + selectedColor: PropTypes.string, + selectedColorHandler: PropTypes.func +}; + +export default SpectrumColorPicker; diff --git a/ColorPickerPOC/ColorPickerSpectrum.module.less b/ColorPickerPOC/ColorPickerSpectrum.module.less new file mode 100644 index 0000000000..b7dcabdf0f --- /dev/null +++ b/ColorPickerPOC/ColorPickerSpectrum.module.less @@ -0,0 +1,22 @@ +// ColorPickerSpectrum.module.less +// +@import '../styles/colors.less'; +@import '../styles/mixins.less'; +@import '../styles/variables.less'; + +.colorPicker { + position: relative; +} + +.circleIndicator { + border: 3px solid #808080; + height: 48px; + border-radius: 99px; + pointer-events: none; + position: absolute; + width: 48px; + + .focus({ + border: 6px solid #000; + }) +} diff --git a/ColorPickerPOC/SpectrumIndicator.js b/ColorPickerPOC/SpectrumIndicator.js new file mode 100644 index 0000000000..1dc5625b40 --- /dev/null +++ b/ColorPickerPOC/SpectrumIndicator.js @@ -0,0 +1,114 @@ +import {is} from '@enact/core/keymap'; +import Spottable from '@enact/spotlight/Spottable'; +import spotlight from '@enact/spotlight'; +import PropTypes from 'prop-types'; +import {useCallback, useEffect} from 'react'; + +import {getHexColorFromGradient} from './utils'; + +import css from './ColorPickerSpectrum.module.less'; + +const SpottableDiv = Spottable('div'); + +const CircleIndicator = ({bgColor, canvasRef, isIndicatorActive, selectedColorHandler, setIsIndicatorActive, setIndicatorBgColor, setX, setY, x, y}) => { + + // resume spotlight when indicator is not active + useEffect(() => { + if (!isIndicatorActive) { + spotlight.resume(); + } + }, [isIndicatorActive]); + + const handleOnKeyDown = useCallback(({keyCode}) => { + if (is('enter', keyCode)) { // set indicator in active state and pause spotlight so no other containers get focus when selecting a color with 5-way + setIsIndicatorActive(!isIndicatorActive); + spotlight.pause(); + } else if (is('down', keyCode)) { + const hexColor = getHexColorFromGradient(canvasRef, x, y); + setIndicatorBgColor(hexColor); + } else if (is('up', keyCode)) { + const hexColor = getHexColorFromGradient(canvasRef, x, y); + setIndicatorBgColor(hexColor); + } else if (is('left', keyCode)) { + const hexColor = getHexColorFromGradient(canvasRef, x, y); + setIndicatorBgColor(hexColor); + } else if (is('right', keyCode)) { + const hexColor = getHexColorFromGradient(canvasRef, x, y); + setIndicatorBgColor(hexColor); + } + }, [canvasRef, x, y, isIndicatorActive, setIsIndicatorActive, setIndicatorBgColor]); + + const handleOnKeyUp = useCallback(({keyCode}) => { + if (is('down', keyCode)) { + const hexColor = getHexColorFromGradient(canvasRef, x, y); + selectedColorHandler(hexColor); + } else if (is('up', keyCode)) { + const hexColor = getHexColorFromGradient(canvasRef, x, y); + selectedColorHandler(hexColor); + } else if (is('left', keyCode)) { + const hexColor = getHexColorFromGradient(canvasRef, x, y); + selectedColorHandler(hexColor); + } else if (is('right', keyCode)) { + const hexColor = getHexColorFromGradient(canvasRef, x, y); + selectedColorHandler(hexColor); + } + }, [canvasRef, selectedColorHandler, x, y]); + + const handleSpotlightDown = useCallback(() => { + if (isIndicatorActive && y < canvasRef.current.clientHeight - 1) { + setY(y++); + } + }, [canvasRef, isIndicatorActive, setY, y]); + + const handleSpotlightLeft = useCallback(() => { + if (isIndicatorActive && x > 0) { + setX(x--); + } + }, [isIndicatorActive, setX, x]); + + const handleSpotlightRight = useCallback(() => { + if (isIndicatorActive && x < canvasRef.current.clientWidth) { + setX(x++); + } + }, [canvasRef, isIndicatorActive, setX, x]); + + const handleSpotlightUp = useCallback(() => { + if (isIndicatorActive && y > 0) { + setY(y--); + } + }, [isIndicatorActive, setY, y]); + + return ( + + ); +}; + +CircleIndicator.displayName = 'CircleIndicator'; +CircleIndicator.propTypes = { + bgColor: PropTypes.string, + canvasRef: PropTypes.any, + isIndicatorActive: PropTypes.bool, + selectedColorHandler: PropTypes.func, + setIndicatorBgColor: PropTypes.func, + setIsIndicatorActive: PropTypes.func, + setX: PropTypes.func, + setY: PropTypes.func, + x: PropTypes.number, + y: PropTypes.number +}; + +export default CircleIndicator; diff --git a/ColorPickerPOC/utils.js b/ColorPickerPOC/utils.js index 4722d7f833..5892b544e2 100644 --- a/ColorPickerPOC/utils.js +++ b/ColorPickerPOC/utils.js @@ -12,6 +12,16 @@ function rgbStringToHex (rgbString) { return ("#" + a[0] + a[1] + a[2]).toUpperCase(); } +function rgbToHex (r, g, b) { + return '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join(''); +} + +const getHexColorFromGradient = (canvasRef, x, y) => { + const ctx = canvasRef.current.getContext('2d'); + const imageData = ctx.getImageData(x, y, 1, 1); + return rgbToHex(imageData.data[0], imageData.data[1], imageData.data[2]); +}; + const generateOppositeColor = (hexColor) => { hexColor = hexColor.replace('#', ''); @@ -46,6 +56,7 @@ const rgbObjectToHex = (rgbColor) => { export { generateOppositeColor, + getHexColorFromGradient, hexToRGB, rgbObjectToHex, rgbStringToHex From 719bbf7330fa3caf7222f17ff419c80281cc1292 Mon Sep 17 00:00:00 2001 From: paul-beldean-lgp <77739822+paul-beldean-lgp@users.noreply.github.com> Date: Fri, 9 Aug 2024 12:59:08 +0300 Subject: [PATCH 07/24] WRQ-30519: Added accelerator to spectrum color picker (#1659) * *WIP* implement spectrum color picker * added positionPointer, wrapped ColorPicker in Spottable, code cleanup * fixed spottable div * *WIP* added 5-way navigation to indicator * color selection via 5-way, code cleanup, fix lint * added accelerator to spectrum color picker * fixed review issues --------- Co-authored-by: Adrian Cocoara Co-authored-by: Daniel Stoian --- ColorPickerPOC/SpectrumIndicator.js | 50 +++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/ColorPickerPOC/SpectrumIndicator.js b/ColorPickerPOC/SpectrumIndicator.js index 1dc5625b40..d03f51f4aa 100644 --- a/ColorPickerPOC/SpectrumIndicator.js +++ b/ColorPickerPOC/SpectrumIndicator.js @@ -2,7 +2,7 @@ import {is} from '@enact/core/keymap'; import Spottable from '@enact/spotlight/Spottable'; import spotlight from '@enact/spotlight'; import PropTypes from 'prop-types'; -import {useCallback, useEffect} from 'react'; +import {useCallback, useEffect, useState} from 'react'; import {getHexColorFromGradient} from './utils'; @@ -11,6 +11,9 @@ import css from './ColorPickerSpectrum.module.less'; const SpottableDiv = Spottable('div'); const CircleIndicator = ({bgColor, canvasRef, isIndicatorActive, selectedColorHandler, setIsIndicatorActive, setIndicatorBgColor, setX, setY, x, y}) => { + const [holding, setHolding] = useState(false); + const [prevKey, setPrevKey] = useState(''); + const [stepValue, setStepValue] = useState(1); // resume spotlight when indicator is not active useEffect(() => { @@ -36,7 +39,22 @@ const CircleIndicator = ({bgColor, canvasRef, isIndicatorActive, selectedColorHa const hexColor = getHexColorFromGradient(canvasRef, x, y); setIndicatorBgColor(hexColor); } - }, [canvasRef, x, y, isIndicatorActive, setIsIndicatorActive, setIndicatorBgColor]); + + if (isIndicatorActive) { + if (holding) { + if (prevKey === keyCode) { + if (stepValue < 10) { + setStepValue(prevValue => prevValue + 1); + } + } else { + setStepValue(1); + } + } else { + setHolding(true); + setPrevKey(keyCode); + } + } + }, [canvasRef, holding, isIndicatorActive, prevKey, setIndicatorBgColor, setIsIndicatorActive, stepValue, x, y]); const handleOnKeyUp = useCallback(({keyCode}) => { if (is('down', keyCode)) { @@ -52,31 +70,35 @@ const CircleIndicator = ({bgColor, canvasRef, isIndicatorActive, selectedColorHa const hexColor = getHexColorFromGradient(canvasRef, x, y); selectedColorHandler(hexColor); } + + setHolding(false); + setPrevKey(''); + setStepValue(1); }, [canvasRef, selectedColorHandler, x, y]); const handleSpotlightDown = useCallback(() => { - if (isIndicatorActive && y < canvasRef.current.clientHeight - 1) { - setY(y++); + if (isIndicatorActive && y + stepValue <= canvasRef.current.clientHeight - 1) { + setY(y + stepValue); } - }, [canvasRef, isIndicatorActive, setY, y]); + }, [canvasRef, isIndicatorActive, setY, stepValue, y]); const handleSpotlightLeft = useCallback(() => { - if (isIndicatorActive && x > 0) { - setX(x--); + if (isIndicatorActive && x - stepValue >= 0) { + setX(x - stepValue); } - }, [isIndicatorActive, setX, x]); + }, [isIndicatorActive, setX, stepValue, x]); const handleSpotlightRight = useCallback(() => { - if (isIndicatorActive && x < canvasRef.current.clientWidth) { - setX(x++); + if (isIndicatorActive && x + stepValue <= canvasRef.current.clientWidth) { + setX(x + stepValue); } - }, [canvasRef, isIndicatorActive, setX, x]); + }, [canvasRef, isIndicatorActive, setX, stepValue, x]); const handleSpotlightUp = useCallback(() => { - if (isIndicatorActive && y > 0) { - setY(y--); + if (isIndicatorActive && y - stepValue >= 0) { + setY(y - stepValue); } - }, [isIndicatorActive, setY, y]); + }, [isIndicatorActive, setY, stepValue, y]); return ( Date: Fri, 9 Aug 2024 16:10:45 +0300 Subject: [PATCH 08/24] WRQ-30515: Added handler to display colors state in actions log when favorite color is removed (#1660) * Created component skeleton for ColorPickerPOC * Added propTypes * Fixed lint warnings * Added option to remove favorite color on long press * Applied design changes for the Favorite Colors section * Added shake animation for delete action * Added shake animation for delete action * Added handler to display colors state in actions log when favorite color is removed * Added pointer events * Review fixes * Changed alignment for favorite colors * Fixed spotlight navigation on favorite colors --- ColorPickerPOC/ColorPickerPOC.js | 71 ++++++++++++++--------- ColorPickerPOC/ColorPickerPOC.module.less | 35 ++++++----- 2 files changed, 63 insertions(+), 43 deletions(-) diff --git a/ColorPickerPOC/ColorPickerPOC.js b/ColorPickerPOC/ColorPickerPOC.js index ca73776a59..d8b6d5931c 100644 --- a/ColorPickerPOC/ColorPickerPOC.js +++ b/ColorPickerPOC/ColorPickerPOC.js @@ -20,24 +20,22 @@ import componentsCss from './ColorPickerPOC.module.less'; const SpottableButton = Spottable(ButtonBase); -const FavoriteColors = ({colorHandler, colors = [], selectedColor = '#3455eb', selectedColorHandler}) => { +const FavoriteColors = ({favoriteColors = [], favoriteColorsHandler, selectedColor = '#3455eb', selectedColorHandler}) => { const [clickEnabled, setClickEnabled] = useState(true); const [editEnabled, setEditEnabled] = useState(false); - const [favoriteColors, setFavoriteColors] = useState(colors); const shakeEffectRef = useRef(null); const timerRef = useRef(null); const addNewFavoriteColor = useCallback(() => { if (favoriteColors.includes(selectedColor)) return; - setFavoriteColors(prevState => { - const colorsState = [...prevState, selectedColor]; + favoriteColorsHandler(() => { + const colorsState = [...favoriteColors, selectedColor]; if (colorsState.length > 8) colorsState.shift(); - colorHandler({selectedColor, favoriteColors: colorsState}); return colorsState; }); - }, [colorHandler, favoriteColors, selectedColor]); + }, [favoriteColors, favoriteColorsHandler, selectedColor]); const onAddNewFavoriteColor = useCallback(() => { if (editEnabled) { @@ -51,32 +49,40 @@ const FavoriteColors = ({colorHandler, colors = [], selectedColor = '#3455eb', s if (!clickEnabled) return; const targetId = ev.target.offsetParent.id || ev.target.id; const [buttonColor, buttonIndex] = targetId.split('-'); + if (editEnabled && clickEnabled) { - setFavoriteColors(prevState => - prevState.filter((stateColor, index) => { - return !(stateColor === buttonColor && index === Number(buttonIndex)); - })); + const filteredColors = favoriteColors.filter((color, index) => { + return !(color === buttonColor && index === Number(buttonIndex)); + }); + + favoriteColorsHandler(filteredColors); + selectedColorHandler(selectedColor); return; } + + favoriteColorsHandler(favoriteColors); selectedColorHandler(buttonColor); - colorHandler({currentColor: buttonColor, favoriteColors}); - }, [clickEnabled, colorHandler, editEnabled, favoriteColors, selectedColorHandler]); + }, [clickEnabled, editEnabled, favoriteColors, favoriteColorsHandler, selectedColor, selectedColorHandler]); - const onMouseDown = useCallback((ev) => { + const onPressHandler = useCallback((ev) => { if (editEnabled) return; const target = ev.target.id ? ev.target : ev.target.offsetParent; + shakeEffectRef.current = setTimeout(() => { target.classList.add(componentsCss.shakeFavoriteColor); }, 300); + timerRef.current = setTimeout(() => { setEditEnabled(true); setClickEnabled(false); + target.classList.remove(componentsCss.shakeFavoriteColor); }, 1000); }, [editEnabled]); - const onMouseUp = useCallback((ev) => { + const onReleaseHandler = useCallback((ev) => { const target = ev.target.id ? ev.target : ev.target.offsetParent; target.classList.remove(componentsCss.shakeFavoriteColor); + clearTimeout(shakeEffectRef.current); clearTimeout(timerRef.current); setTimeout(() => { @@ -87,7 +93,7 @@ const FavoriteColors = ({colorHandler, colors = [], selectedColor = '#3455eb', s return (
- + {favoriteColors.slice(4, 8).map((color, index) => { return ( - + {favoriteColors.slice(0, 4).map((color, index) => { return ( - + - {editEnabled ? 'check' : 'plus'} + {editEnabled ? 'check' : 'plus'}
@@ -156,19 +166,22 @@ FavoriteColors.propTypes = { colorHandler: PropTypes.func, colors: PropTypes.array, css: PropTypes.object, + favoriteColors: PropTypes.array, + favoriteColorsHandler: PropTypes.func, selectedColor: PropTypes.string, selectedColorHandler: PropTypes.func }; const ColorPickerPOCBase = ({color = '#eb4034', colors = [], css, onChangeColor, open, ...rest}) => { + const [favoriteColors, setFavoriteColors] = useState(colors); const [selectedColor, setSelectedColor] = useState(color); useEffect(() => { - if (selectedColor) { - onChangeColor({currentColor: selectedColor}); + if (selectedColor || favoriteColors) { + onChangeColor({selectedColor, favoriteColors}); } - }, [onChangeColor, selectedColor]); + }, [favoriteColors, onChangeColor, selectedColor]); return ( @@ -198,6 +211,8 @@ const ColorPickerPOCBase = ({color = '#eb4034', colors = [], css, onChangeColor, colorHandler={onChangeColor} colors={colors} css={css} + favoriteColors={favoriteColors} + favoriteColorsHandler={setFavoriteColors} selectedColor={selectedColor} selectedColorHandler={setSelectedColor} /> diff --git a/ColorPickerPOC/ColorPickerPOC.module.less b/ColorPickerPOC/ColorPickerPOC.module.less index 9a3923adcf..0dcb981d00 100644 --- a/ColorPickerPOC/ColorPickerPOC.module.less +++ b/ColorPickerPOC/ColorPickerPOC.module.less @@ -7,24 +7,29 @@ min-width: 800px; } -.currentColor { - border-radius: 24px; - border: 9px solid; - height: 240px; - transition: transform .2s; - width: 100%; - - .currentColorIcon { - transition: transform .2s; - } +.selectedColorColumn { + margin-top: 15px; - .focus({ - box-shadow: 0 0 12px 6px inset; + .selectedColor { + border-radius: 24px; + border: 9px solid; + height: 240px; + margin-inline: 0; + transition: transform .2s; + width: 95%; - .currentColorIcon { - transform: scale(1.2); + .selectedColorIcon { + transition: transform .2s; } - }); + + .focus({ + box-shadow: 0 0 12px 6px inset; + + .selectedColorIcon { + transform: scale(1.2); + } + }); + } } .favoriteColorsRow { From 716f51b4ffd015e8b9a9d322cf570297ccf17bbc Mon Sep 17 00:00:00 2001 From: paul-beldean-lgp <77739822+paul-beldean-lgp@users.noreply.github.com> Date: Fri, 9 Aug 2024 17:09:44 +0300 Subject: [PATCH 09/24] fixed spectrum canvas size (#1662) --- ColorPickerPOC/ColorPickerSpectrum.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ColorPickerPOC/ColorPickerSpectrum.js b/ColorPickerPOC/ColorPickerSpectrum.js index b3c877cfe2..3ce06e140c 100644 --- a/ColorPickerPOC/ColorPickerSpectrum.js +++ b/ColorPickerPOC/ColorPickerSpectrum.js @@ -1,3 +1,4 @@ +import ri from '@enact/ui/resolution'; import PropTypes from 'prop-types'; import {useCallback, useEffect, useRef, useState} from 'react'; @@ -107,13 +108,13 @@ const SpectrumColorPicker = (props) => {
Date: Fri, 9 Aug 2024 17:48:46 +0300 Subject: [PATCH 10/24] WRQ-30517: Create a slider type for webOS ColorPicker (part2) (#1661) * Fixed problem with knob size and added a version of HSL for ColorPickerSlider. * Small fix * Review fixes --- ColorPickerPOC/ColorPickerPOC.js | 3 +- ColorPickerPOC/ColorPickerSlider.js | 252 +++++++++++++++++-- ColorPickerPOC/ColorPickerSlider.module.less | 31 ++- ColorPickerPOC/utils.js | 121 +++++++++ 4 files changed, 376 insertions(+), 31 deletions(-) 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 }; From a22153608f4df224a8acd04400f5c01dfd71db5a Mon Sep 17 00:00:00 2001 From: adrian-cocoara-lgp <63335823+adrian-cocoara-lgp@users.noreply.github.com> Date: Fri, 16 Aug 2024 13:58:38 +0300 Subject: [PATCH 11/24] WRQ-32413: [POC] webOS ColorPicker - add responsiveness (#1668) * handle resize of canvas * code cleanup * sorted props --------- Co-authored-by: Stanca --- ColorPickerPOC/ColorPickerSpectrum.js | 22 ++++++++++++++----- .../ColorPickerSpectrum.module.less | 6 +++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/ColorPickerPOC/ColorPickerSpectrum.js b/ColorPickerPOC/ColorPickerSpectrum.js index 3ce06e140c..4c6e2d1d5e 100644 --- a/ColorPickerPOC/ColorPickerSpectrum.js +++ b/ColorPickerPOC/ColorPickerSpectrum.js @@ -10,10 +10,12 @@ import css from './ColorPickerSpectrum.module.less'; const SpectrumColorPicker = (props) => { const {selectedColor, selectedColorHandler} = props; const canvasRef = useRef(null); + const [canvasHeight, setCanvasHeight] = useState(ri.scale(660)); + const [canvasWidth, setCanvasWidth] = useState(ri.scale(800)); + const [indicatorBgColor, setIndicatorBgColor] = useState('transparent'); const [indicatorX, setIndicatorX] = useState(0); const [indicatorY, setIndicatorY] = useState(0); const [isDragging, setIsDragging] = useState(false); - const [indicatorBgColor, setIndicatorBgColor] = useState('transparent'); const [isIndicatorActive, setIsIndicatorActive] = useState(false); useEffect(() => { @@ -57,7 +59,15 @@ const SpectrumColorPicker = (props) => { } }; positionIndicator(); - }, []); // eslint-disable-line react-hooks/exhaustive-deps + + const handleResize = () => { + setCanvasHeight(canvas.parentElement.clientHeight); + setCanvasWidth(canvas.parentElement.clientWidth); + }; + + window.addEventListener('resize', handleResize); + handleResize(); + }, [canvasHeight, canvasWidth]); // eslint-disable-line react-hooks/exhaustive-deps const handleCanvasPointerDown = useCallback((e) => { const canvas = canvasRef.current; @@ -107,14 +117,14 @@ const SpectrumColorPicker = (props) => { return (
Date: Thu, 22 Aug 2024 16:05:40 +0300 Subject: [PATCH 12/24] WRQ-32412: WebOS ColorPicker fixes and improvements (#1672) * Added useEffect, so the sliders change when selecting a new color from favorites. * Added a switch to chose between HSL or RGB format. * Revert npm-shrinkwrap override * style improvements * spacing adjustments --------- Co-authored-by: Stanca --- ColorPickerPOC/ColorPickerGrid.module.less | 6 +- ColorPickerPOC/ColorPickerPOC.js | 17 +- ColorPickerPOC/ColorPickerPOC.module.less | 7 +- ColorPickerPOC/ColorPickerSlider.js | 251 +++++++++++------- ColorPickerPOC/ColorPickerSlider.module.less | 71 +++-- .../ColorPickerSpectrum.module.less | 6 +- ColorPickerPOC/utils.js | 4 +- 7 files changed, 203 insertions(+), 159 deletions(-) diff --git a/ColorPickerPOC/ColorPickerGrid.module.less b/ColorPickerPOC/ColorPickerGrid.module.less index f1631128d5..c9dde7c624 100644 --- a/ColorPickerPOC/ColorPickerGrid.module.less +++ b/ColorPickerPOC/ColorPickerGrid.module.less @@ -1,8 +1,8 @@ // ColorPicker.module.less // -@import '../styles/colors.less'; -@import '../styles/mixins.less'; -@import '../styles/variables.less'; +@import "../styles/colors.less"; +@import "../styles/mixins.less"; +@import "../styles/variables.less"; .colorPicker { .colorsColumn { diff --git a/ColorPickerPOC/ColorPickerPOC.js b/ColorPickerPOC/ColorPickerPOC.js index ce7c26ee1c..5e6b799055 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, {ColorPickerSliderHSL} from './ColorPickerSlider'; // eslint-disable-line no-unused-vars +import ColorPickerSlider from './ColorPickerSlider'; import ColorPickerSpectrum from './ColorPickerSpectrum'; import {generateOppositeColor} from './utils'; @@ -184,29 +184,28 @@ const ColorPickerPOCBase = ({color = '#eb4034', colors = [], css, onChangeColor, }, [favoriteColors, onChangeColor, selectedColor]); return ( - + - - - + + +
- +
- +
- {/* */}
- + { hsla(${hue}, ${saturation}%, 100%, 1))`; }; -const ColorPickerSlider = ({selectedColor, selectedColorHandler, ...props}) => { +const ColorPickerSliderRGB = ({selectedColor, selectedColorHandler, ...props}) => { const {red, green, blue} = hexToRGB(selectedColor); const [localRed, setLocalRed] = useState(red); const [localGreen, setLocalGreen] = useState(green); const [localBlue, setLocalBlue] = useState(blue); + useEffect(() => { + setLocalRed(red); + setLocalGreen(green); + setLocalBlue(blue); + }, [blue, green, red]); + const changeValueRed = useCallback((ev) => { setLocalRed(ev.value); }, []); @@ -82,22 +89,24 @@ const ColorPickerSlider = ({selectedColor, selectedColorHandler, ...props}) => { }, [localBlue, localGreen, localRed, selectedColorHandler]); return ( -
+ Red { value={localRed} /> - {localRed} + {localRed} - - Green { value={localGreen} /> - {localGreen} + {localGreen} - - Blue { value={localBlue} /> - {localBlue} + {localBlue} -
+ ); }; @@ -177,6 +184,12 @@ const ColorPickerSliderHSL = ({selectedColor, selectedColorHandler, ...props}) = const [saturation, setSaturation] = useState(s); const [lightness, setLightness] = useState(l); + useEffect(() => { + setHue(h); + setSaturation(s); + setLightness(l); + }, [h, l, s]); + const changeValueHue = useCallback((ev) => { setHue(ev.value); }, []); @@ -194,91 +207,121 @@ const ColorPickerSliderHSL = ({selectedColor, selectedColorHandler, ...props}) = }, [hue, saturation, lightness, selectedColorHandler]); return ( -
+ - Hue - - - - - {hue} - + + Hue + + + + + {hue}% + + + + Saturation + + + + + {saturation}% + + + + Lightness + + + + + {lightness}% + + - - Saturation - - - - - {saturation}% - - - - Lightness - - - - - {lightness}% - - -
+ + ); +}; + +const ColorPickerSlider = ({selectedColor, selectedColorHandler, ...props}) => { + const [pickerType, setPickerType] = useState(false); + + const handleSwitch = useCallback(() => { + setPickerType(type => !type); + }, [setPickerType]); + + return ( + + + {pickerType ? 'HSL Picker' : 'RGB picker'} + + {pickerType ? + : + + } + ); }; @@ -292,8 +335,14 @@ ColorPickerSliderHSL.propTypes = { selectedColorHandler: PropTypes.func }; +ColorPickerSliderRGB.propTypes = { + selectedColor: PropTypes.string, + selectedColorHandler: PropTypes.func +}; + export { ColorPickerSlider, - ColorPickerSliderHSL + ColorPickerSliderHSL, + ColorPickerSliderRGB }; export default ColorPickerSlider; diff --git a/ColorPickerPOC/ColorPickerSlider.module.less b/ColorPickerPOC/ColorPickerSlider.module.less index 2ba045213b..6c74cd8175 100644 --- a/ColorPickerPOC/ColorPickerSlider.module.less +++ b/ColorPickerPOC/ColorPickerSlider.module.less @@ -1,53 +1,46 @@ -.cellElement { - margin-top: 21px; +@import "../styles/mixins.less"; + +.switchCell { + margin-top: -63px; + padding: 0 30px; } .labelText { - font-size: 42px; - margin-bottom: 12px; + font-size: 42px; + margin: 9px 0; } .outputText { - align-content: center; - background-color: #eeeeee; - border-radius: 12px; - color: #444444; - display: grid; - justify-content: end; - 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; + display: flex; + justify-content: center; + align-items: center; + background-color: #eeeeee; + border-radius: 12px; + margin-left: 30px; } .slider { - --sand-progress-bg-color-rgb: transparent; - border-radius: 99px; - height: 42px; - margin-left: 18px; - margin-right: 18px; + --sand-progress-bg-color-rgb: transparent; + border-radius: 99px; + height: 33px; + margin-left: 18px; + margin-right: 18px; - .knob { - &::before { - border-radius: 99px; - height: 120px; - width: 120px; - } - } -} + .focus({ + .knob { + transform: scale(1.1); + } + }); -.sliderCell { - border-radius: 99px; + .knob { + &::before { + border-radius: 99px; + height: 102px; + width: 102px; + } + } } -.sliderContainer { - width: 900px; +.sliderCell { + border-radius: 99px; } diff --git a/ColorPickerPOC/ColorPickerSpectrum.module.less b/ColorPickerPOC/ColorPickerSpectrum.module.less index 6dca72af1a..47fddea143 100644 --- a/ColorPickerPOC/ColorPickerSpectrum.module.less +++ b/ColorPickerPOC/ColorPickerSpectrum.module.less @@ -1,8 +1,8 @@ // ColorPickerSpectrum.module.less // -@import '../styles/colors.less'; -@import '../styles/mixins.less'; -@import '../styles/variables.less'; +@import "../styles/colors.less"; +@import "../styles/mixins.less"; +@import "../styles/variables.less"; .colorPicker { position: relative; diff --git a/ColorPickerPOC/utils.js b/ColorPickerPOC/utils.js index 97163dfb1b..c21780a9de 100644 --- a/ColorPickerPOC/utils.js +++ b/ColorPickerPOC/utils.js @@ -107,7 +107,7 @@ const hslToHex = ({h, s, l}) => { r = 0; g = x; b = c; } else if (240 <= h && h < 300) { r = x; g = 0; b = c; - } else if (300 <= h && h < 360) { + } else if (300 <= h && h <= 360) { r = c; g = 0; b = x; } // Having obtained RGB, convert channels to hex @@ -152,7 +152,7 @@ const hslToRGBString = ({h, s, l}) => { r = 0; g = x; b = c; } else if (240 <= h && h < 300) { r = x; g = 0; b = c; - } else if (300 <= h && h < 360) { + } else if (300 <= h && h <= 360) { r = c; g = 0; b = x; } From 2abd1547e6356b60170aa7f4fc406f3f06d10efe Mon Sep 17 00:00:00 2001 From: Daniel Stoian <63335068+daniel-stoian-lgp@users.noreply.github.com> Date: Mon, 26 Aug 2024 09:55:18 +0300 Subject: [PATCH 13/24] WRQ-32437: Fixed deletion of favorite colors for ColorPicker (#1676) * fixed deletion of favorite colors for ColorPicker * save component state when props change * removed unnecessary code * added back useEffect * changed outputText color to be more visible * added handler for Enter key to edit favorite colors * code review fix --------- Co-authored-by: Stanca --- ColorPickerPOC/ColorPickerPOC.js | 37 ++++++++++++-------- ColorPickerPOC/ColorPickerSlider.module.less | 1 + 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/ColorPickerPOC/ColorPickerPOC.js b/ColorPickerPOC/ColorPickerPOC.js index 5e6b799055..923db0ff83 100644 --- a/ColorPickerPOC/ColorPickerPOC.js +++ b/ColorPickerPOC/ColorPickerPOC.js @@ -66,17 +66,19 @@ const FavoriteColors = ({favoriteColors = [], favoriteColorsHandler, selectedCol const onPressHandler = useCallback((ev) => { if (editEnabled) return; - const target = ev.target.id ? ev.target : ev.target.offsetParent; - - shakeEffectRef.current = setTimeout(() => { - target.classList.add(componentsCss.shakeFavoriteColor); - }, 300); - - timerRef.current = setTimeout(() => { - setEditEnabled(true); - setClickEnabled(false); - target.classList.remove(componentsCss.shakeFavoriteColor); - }, 1000); + if (ev.type === 'pointerdown' || (ev.type === 'keydown' && ev.keyCode === 13)) { + const target = ev.target.id ? ev.target : ev.target.offsetParent; + + shakeEffectRef.current = setTimeout(() => { + target.classList.add(componentsCss.shakeFavoriteColor); + }, 300); + + timerRef.current = setTimeout(() => { + setEditEnabled(true); + setClickEnabled(false); + target.classList.remove(componentsCss.shakeFavoriteColor); + }, 1000); + } }, [editEnabled]); const onReleaseHandler = useCallback((ev) => { @@ -102,8 +104,8 @@ const FavoriteColors = ({favoriteColors = [], favoriteColorsHandler, selectedCol key={`${color}_${index + 4}`} minWidth={false} onClick={onSelectFavoriteColor} - onMouseDown={onPressHandler} - onMouseUp={onReleaseHandler} + onKeyDown={onPressHandler} + onKeyUp={onReleaseHandler} onPointerDown={onPressHandler} onPointerUp={onReleaseHandler} size="small" @@ -127,8 +129,8 @@ const FavoriteColors = ({favoriteColors = [], favoriteColorsHandler, selectedCol key={`${color}_${index}`} minWidth={false} onClick={onSelectFavoriteColor} - onMouseDown={onPressHandler} - onMouseUp={onReleaseHandler} + onKeyDown={onPressHandler} + onKeyUp={onReleaseHandler} onPointerDown={onPressHandler} onPointerUp={onReleaseHandler} size="small" @@ -177,6 +179,11 @@ const ColorPickerPOCBase = ({color = '#eb4034', colors = [], css, onChangeColor, const [favoriteColors, setFavoriteColors] = useState(colors); const [selectedColor, setSelectedColor] = useState(color); + useEffect(() => { + setFavoriteColors(colors); + setSelectedColor(color); + }, [color, colors]); + useEffect(() => { if (selectedColor || favoriteColors) { onChangeColor({selectedColor, favoriteColors}); diff --git a/ColorPickerPOC/ColorPickerSlider.module.less b/ColorPickerPOC/ColorPickerSlider.module.less index 6c74cd8175..21a6124200 100644 --- a/ColorPickerPOC/ColorPickerSlider.module.less +++ b/ColorPickerPOC/ColorPickerSlider.module.less @@ -15,6 +15,7 @@ justify-content: center; align-items: center; background-color: #eeeeee; + color: rgb(76, 80, 89); border-radius: 12px; margin-left: 30px; } From 8806de28922cd3a6f7d06dccb3624eb2eb57dd11 Mon Sep 17 00:00:00 2001 From: alexandru-morariu-lgp <84004128+alexandrumorariu@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:35:57 +0300 Subject: [PATCH 14/24] WRR-1870: Adjust Slider ColorPicker based on feedback (#1684) * Added a dropdown to chose between HSL or RGB format and an input for hex values. * styling fixes --------- Co-authored-by: Stanca --- ColorPickerPOC/ColorPickerSlider.js | 61 ++++++++++---- ColorPickerPOC/ColorPickerSlider.module.less | 88 +++++++++++++------- ColorPickerPOC/utils.js | 5 ++ 3 files changed, 104 insertions(+), 50 deletions(-) diff --git a/ColorPickerPOC/ColorPickerSlider.js b/ColorPickerPOC/ColorPickerSlider.js index 50b5727fa8..f3c68666bf 100644 --- a/ColorPickerPOC/ColorPickerSlider.js +++ b/ColorPickerPOC/ColorPickerSlider.js @@ -2,12 +2,13 @@ import Layout, {Cell, Row} from '@enact/ui/Layout'; import PropTypes from 'prop-types'; import {useCallback, useEffect, useState} from 'react'; +import Dropdown from '../Dropdown'; import Slider from '../Slider'; -import SwitchItem from '../SwitchItem'; -import {generateOppositeColor, hexToHSL, hexToRGB, hslToHex, hslToRGBString, rgbObjectToHex} from './utils'; +import {checkHex, generateOppositeColor, hexToHSL, hexToRGB, hslToHex, hslToRGBString, rgbObjectToHex} from './utils'; import componentCss from './ColorPickerSlider.module.less'; +import {InputField} from '../Input'; const hueGradient = (saturation, lightness) => { return `linear-gradient(to right, @@ -89,7 +90,7 @@ const ColorPickerSliderRGB = ({selectedColor, selectedColorHandler, ...props}) = }, [localBlue, localGreen, localRed, selectedColorHandler]); return ( - + Red @@ -207,7 +208,7 @@ const ColorPickerSliderHSL = ({selectedColor, selectedColorHandler, ...props}) = }, [hue, saturation, lightness, selectedColorHandler]); return ( - + Hue @@ -235,7 +236,7 @@ const ColorPickerSliderHSL = ({selectedColor, selectedColorHandler, ...props}) = value={hue} /> - {hue}% + {hue} @@ -302,23 +303,47 @@ const ColorPickerSliderHSL = ({selectedColor, selectedColorHandler, ...props}) = }; const ColorPickerSlider = ({selectedColor, selectedColorHandler, ...props}) => { - const [pickerType, setPickerType] = useState(false); + const [pickerType, setPickerType] = useState('RGB Picker'); - const handleSwitch = useCallback(() => { - setPickerType(type => !type); + const handleBlur = useCallback(() => { + if (checkHex(selectedColor)) selectedColorHandler('#000000'); + }, [selectedColor, selectedColorHandler]); + + const handleInputChange = useCallback((ev) => { + if (ev.value.length > 7 || ev.value.length < 1) return; + selectedColorHandler(ev.value); + }, [selectedColorHandler]); + + const handleSelect = useCallback((ev) => { + setPickerType(ev.data); }, [setPickerType]); return ( - - - {pickerType ? 'HSL Picker' : 'RGB picker'} - - {pickerType ? - : + + + + + {['RGB Picker', 'HSL Picker']} + + + + + + + {pickerType === 'HSL Picker' ? + : } diff --git a/ColorPickerPOC/ColorPickerSlider.module.less b/ColorPickerPOC/ColorPickerSlider.module.less index 21a6124200..b327988ace 100644 --- a/ColorPickerPOC/ColorPickerSlider.module.less +++ b/ColorPickerPOC/ColorPickerSlider.module.less @@ -1,47 +1,71 @@ @import "../styles/mixins.less"; -.switchCell { +.sliderPickerContainer { margin-top: -63px; - padding: 0 30px; -} -.labelText { - font-size: 42px; - margin: 9px 0; + .containerRow { + padding-bottom: 30px; + } } -.outputText { - display: flex; - justify-content: center; - align-items: center; - background-color: #eeeeee; - color: rgb(76, 80, 89); - border-radius: 12px; - margin-left: 30px; -} +.slidersContainer { + padding: 0 30px; + + .labelText { + font-size: 42px; + margin: 9px 0; + } + + .sliderCell { + border-radius: 99px; + } -.slider { - --sand-progress-bg-color-rgb: transparent; - border-radius: 99px; - height: 33px; - margin-left: 18px; - margin-right: 18px; + .slider { + --sand-progress-bg-color-rgb: transparent; + border-radius: 99px; + height: 33px; + margin-left: 18px; + margin-right: 18px; + + .focus({ + .knob { + transform: scale(1.1); + } + }); - .focus({ .knob { - transform: scale(1.1); + &::before { + border-radius: 99px; + height: 102px; + width: 102px; + } } - }); + } - .knob { - &::before { - border-radius: 99px; - height: 102px; - width: 102px; - } + .outputText { + display: flex; + justify-content: center; + align-items: center; + background-color: #eeeeee; + color: rgb(76, 80, 89); + border-radius: 12px; + margin-left: 30px; } } -.sliderCell { - border-radius: 99px; +.pickerSelect { + margin: 0; + height: 129px; + + [role*=button] { + height: 129px; + } +} + +.hexInput { + width: 402px; + + input { + width: 372px; + } } diff --git a/ColorPickerPOC/utils.js b/ColorPickerPOC/utils.js index c21780a9de..0501c1855c 100644 --- a/ColorPickerPOC/utils.js +++ b/ColorPickerPOC/utils.js @@ -1,3 +1,7 @@ +const checkHex = (hex) => { + return !/^#[0-9A-F]{6}$/i.test(hex); +}; + function rgbStringToHex (rgbString) { if (rgbString.indexOf("rgb(") !== 0) return false; @@ -173,6 +177,7 @@ const rgbObjectToHex = (rgbColor) => { }; export { + checkHex, generateOppositeColor, getHexColorFromGradient, hexToHSL, From 5e4e369ff283ff93e81b5aa78ba8e82e944bbcab Mon Sep 17 00:00:00 2001 From: Stanca <63341832+stanca-pop-lgp@users.noreply.github.com> Date: Mon, 9 Dec 2024 19:25:13 +0200 Subject: [PATCH 15/24] WRR-12000: Enhanced ColorPicker code and made component public (#1759) * added JS Docs * removed @module and indented code * fixed JS docs * fixed `too many doclets` error * fixed js docs warning * fixed JS docs * fixed js docs * added JD docs for SpectrumIndicator.js * refactored rgbStringToHex function and added `willReadFrequently: true` parameter in ColorPickerSpectrum for optimization * made ColorPicker main component public and ColorPickerGrid component props private * fixed qa sampler * removed forgotten console.log * removed transform as it conflicted with 5-way navigation * review fixes * modified example in JS docs * modified JS docs for ColorPicker example * JS docs fix * fixed JS docs * added default colors * fixed typo * fixed JS docs * changed slider names and adjusted tabLayout width * updated deprecated max-width value * moved max-width properties to target tabs class * corrected selector * fixed typo * added `type` and `disabled` props * changed type prop to public * added disabled style and behavior for each subcomponent of ColorPicker * added missing dependency * review fixes * added missing dependency * review fix * WRR-3979: Updated storybook to 8.4 (#1739) * updated storybook to 8.4 * updated to latest storybook * updated storybook-utils * review fix * dummy commit to fix cla license * revert dummy commit * removed spotlightDisabled from Dropdown as it is not recognized as a custom prop * undo spotlightDisabled removal - warning has different cause --------- Co-authored-by: Daniel Stoian <63335068+daniel-stoian-lgp@users.noreply.github.com> --- ColorPicker/ColorPicker.js | 605 ++++++++++-------- ColorPicker/ColorPicker.module.less | 118 ++-- .../ColorPickerGrid.js | 59 +- .../ColorPickerGrid.module.less | 14 +- .../ColorPickerSlider.js | 161 ++++- .../ColorPickerSlider.module.less | 6 +- .../ColorPickerSpectrum.js | 62 +- .../ColorPickerSpectrum.module.less | 0 .../SpectrumIndicator.js | 96 ++- ColorPicker/package.json | 2 +- ColorPicker/utils.js | 273 ++++++-- ColorPickerPOC/ColorPickerPOC.js | 253 -------- ColorPickerPOC/ColorPickerPOC.module.less | 69 -- ColorPickerPOC/package.json | 3 - ColorPickerPOC/utils.js | 189 ------ .../ColorPickerSettingsApp.js | 363 +++++++++++ .../ColorPickerSettingsApp.module.less | 93 +++ ColorPickerSettingsApp/package.json | 3 + .../tests/ColorPickerSettingsApp-specs.js | 54 +- ColorPickerSettingsApp/utils.js | 125 ++++ .../sampler/stories/default/ColorPicker.js | 39 ++ .../sampler/stories/default/ColorPickerPOC.js | 22 - samples/sampler/stories/qa/ColorPicker.js | 12 +- 23 files changed, 1566 insertions(+), 1055 deletions(-) rename {ColorPickerPOC => ColorPicker}/ColorPickerGrid.js (69%) rename {ColorPickerPOC => ColorPicker}/ColorPickerGrid.module.less (56%) rename {ColorPickerPOC => ColorPicker}/ColorPickerSlider.js (78%) rename {ColorPickerPOC => ColorPicker}/ColorPickerSlider.module.less (88%) rename {ColorPickerPOC => ColorPicker}/ColorPickerSpectrum.js (79%) rename {ColorPickerPOC => ColorPicker}/ColorPickerSpectrum.module.less (100%) rename {ColorPickerPOC => ColorPicker}/SpectrumIndicator.js (70%) delete mode 100644 ColorPickerPOC/ColorPickerPOC.js delete mode 100644 ColorPickerPOC/ColorPickerPOC.module.less delete mode 100644 ColorPickerPOC/package.json delete mode 100644 ColorPickerPOC/utils.js create mode 100644 ColorPickerSettingsApp/ColorPickerSettingsApp.js create mode 100644 ColorPickerSettingsApp/ColorPickerSettingsApp.module.less create mode 100644 ColorPickerSettingsApp/package.json rename ColorPicker/tests/ColorPicker-specs.js => ColorPickerSettingsApp/tests/ColorPickerSettingsApp-specs.js (77%) create mode 100644 ColorPickerSettingsApp/utils.js create mode 100644 samples/sampler/stories/default/ColorPicker.js delete mode 100644 samples/sampler/stories/default/ColorPickerPOC.js diff --git a/ColorPicker/ColorPicker.js b/ColorPicker/ColorPicker.js index da6d196762..c9438e188d 100644 --- a/ColorPicker/ColorPicker.js +++ b/ColorPicker/ColorPicker.js @@ -1,181 +1,244 @@ -/* eslint-disable react-hooks/rules-of-hooks */ /** - * Sandstone component to allow the user to choose a color. + * Sandstone component that allows the user to choose a color + * either from a grid, a spectrum, or RGB/HSL color sliders. * * @example * console.log} + * open * /> * * @module sandstone/ColorPicker * @exports ColorPicker * @exports ColorPickerBase - * @exports ColorPickerDecorator - * @private + * @public */ - -import kind from '@enact/core/kind'; import Spottable from '@enact/spotlight/Spottable'; import {Cell, Column, Row} from '@enact/ui/Layout'; -import Toggleable from '@enact/ui/Toggleable'; +import ri from '@enact/ui/resolution'; import PropTypes from 'prop-types'; -import compose from 'ramda/src/compose'; -import {useCallback, useEffect, useState} from 'react'; +import {useCallback, useEffect, useRef, useState} from 'react'; -import BodyText from '../BodyText'; -import Button, {ButtonBase} from '../Button'; +import {ButtonBase} from '../Button'; import Icon from '../Icon'; -import Item from '../Item'; import Popup from '../Popup'; import Skinnable from '../Skinnable'; -import Slider from '../Slider'; +import TabLayout, {Tab} from '../TabLayout'; -import {hexToHSL, HSLToHex} from './utils'; +import ColorPickerGrid from './ColorPickerGrid'; +import ColorPickerSlider from './ColorPickerSlider'; +import ColorPickerSpectrum from './ColorPickerSpectrum'; +import {generateOppositeColor} from './utils'; import componentCss from './ColorPicker.module.less'; const SpottableButton = Spottable(ButtonBase); /** - * A component that contains the content for the {@link sandstone/ColorPicker|ColorPicker} popup. + * The favorite colors component. + * + * This component is most often not used directly but may be composed within another component as it + * is within {@link sandstone/ColorPicker|ColorPicker}. * - * @class PopupContent + * @class FavoriteColors * @memberof sandstone/ColorPicker * @ui * @private */ -const PopupContent = ({color, colorHandler, css, presetColors}) => { - const [hue, setHue] = useState(0); - const [saturation, setSaturation] = useState(0); - const [lightness, setLightness] = useState(0); - - useEffect(() => { - let {h, s, l} = hexToHSL(color); - - setHue(h); - setSaturation(s); - setLightness(l); - }, [color]); +const FavoriteColors = ({disabled, favoriteColors = [], favoriteColorsHandler, selectedColor = '#3455eb', selectedColorHandler}) => { + const [clickEnabled, setClickEnabled] = useState(true); + const [editEnabled, setEditEnabled] = useState(false); + + const shakeEffectRef = useRef(null); + const timerRef = useRef(null); + + const addNewFavoriteColor = useCallback(() => { + if (disabled) return; + if (favoriteColors.includes(selectedColor)) return; + favoriteColorsHandler(() => { + const colorsState = [...favoriteColors, selectedColor]; + if (colorsState.length > 8) colorsState.shift(); + + return colorsState; + }); + }, [disabled, favoriteColors, favoriteColorsHandler, selectedColor]); + + const onAddNewFavoriteColor = useCallback(() => { + if (disabled) return; + if (editEnabled) { + setEditEnabled(false); + return; + } + addNewFavoriteColor(); + }, [addNewFavoriteColor, disabled, editEnabled]); + + const onSelectFavoriteColor = useCallback((ev) => { + if (disabled) return; + if (!clickEnabled) return; + const targetId = ev.target.offsetParent.id || ev.target.id; + const [buttonColor, buttonIndex] = targetId.split('-'); + + if (editEnabled && clickEnabled) { + const filteredColors = favoriteColors.filter((color, index) => { + return !(color === buttonColor && index === Number(buttonIndex)); + }); + + favoriteColorsHandler(filteredColors); + selectedColorHandler(selectedColor); + return; + } - const changeHue = useCallback((ev) => { - setHue(ev.value); - }, []); + favoriteColorsHandler(favoriteColors); + selectedColorHandler(buttonColor); + }, [clickEnabled, disabled, editEnabled, favoriteColors, favoriteColorsHandler, selectedColor, selectedColorHandler]); + + const onPressHandler = useCallback((ev) => { + if (disabled) return; + if (editEnabled) return; + if (ev.type === 'pointerdown' || (ev.type === 'keydown' && ev.keyCode === 13)) { + const target = ev.target.id ? ev.target : ev.target.offsetParent; + + shakeEffectRef.current = setTimeout(() => { + target.classList.add(componentCss.shakeFavoriteColor); + }, 300); + + timerRef.current = setTimeout(() => { + setEditEnabled(true); + setClickEnabled(false); + target.classList.remove(componentCss.shakeFavoriteColor); + }, 1000); + } + }, [disabled, editEnabled]); - const changeLightness = useCallback((ev) => { - setLightness(ev.value); - }, []); + const onReleaseHandler = useCallback((ev) => { + const target = ev.target.id ? ev.target : ev.target.offsetParent; + target.classList.remove(componentCss.shakeFavoriteColor); - const changeSaturation = useCallback((ev) => { - setSaturation(ev.value); + clearTimeout(shakeEffectRef.current); + clearTimeout(timerRef.current); + setTimeout(() => { + setClickEnabled(true); + }, 100); }, []); - const handleClick = useCallback((ev) => { - colorHandler(ev.target.offsetParent.id); - }, [colorHandler]); - - const onSliderValueChange = useCallback(() => { - colorHandler(HSLToHex(hue, saturation, lightness)); - }, [colorHandler, hue, lightness, saturation]); - return ( - - - {presetColors?.map((presetColor, presetColorIndex) => { - - return ( - +
+ + + {favoriteColors.slice(4, 8).map((color, index) => { + return ( + + {editEnabled && trash} + + ); + })} + + + {favoriteColors.slice(0, 4).map((color, index) => { + return ( - - ); - })} + onClick={onSelectFavoriteColor} + onKeyDown={onPressHandler} + onKeyUp={onReleaseHandler} + onPointerDown={onPressHandler} + onPointerUp={onReleaseHandler} + size="small" + spotlightDisabled={disabled} + style={{ + backgroundColor: color, + borderColor: generateOppositeColor(color), + color: generateOppositeColor(color) + }} + > + {editEnabled && trash} + + ); + })} + -
- - Hue {hue} - - Saturation {saturation}% - - Lightness {lightness}% - - -
-
- + + + {editEnabled ? 'check' : 'plus'} + + +
); }; -PopupContent.propTypes = { +FavoriteColors.displayName = 'FavoriteColors'; + +FavoriteColors.propTypes = { /** - * Indicates the color. + * Applies a disabled style and prevents interacting with the component. * - * @type {String} + * @type {Boolean} + * @default false * @private */ - color: PropTypes.string, + disabled: PropTypes.bool, /** - * Called when color is modified. + * Contains an array with the favorite colors. * - * @type {Function} + * @type {Array} * @private */ - colorHandler: PropTypes.func, + favoriteColors: PropTypes.array, /** - * Customizes the component by mapping the supplied collection of CSS class names to the - * corresponding internal elements and states of this component. - * - * The following classes are supported: + * Called when the favorite colors array is modified. * - * `colorPicker` - The root class name - * `coloredDiv` - A class name used for a single div + * @type {Function} + * @private + */ + favoriteColorsHandler: PropTypes.func, + + /** + * Indicates the selected color. * - * @type {Object} + * @type {String} * @private */ - css: PropTypes.object, + selectedColor: PropTypes.string, /** - * Contains an array with a couple of possible preset colors. + * Called when the selected color is modified. * - * @type {Array} + * @type {Function} * @private */ - presetColors: PropTypes.array + selectedColorHandler: PropTypes.func }; /** @@ -187,161 +250,157 @@ PopupContent.propTypes = { * @class ColorPickerBase * @memberof sandstone/ColorPicker * @ui - * @private + * @public */ -const ColorPickerBase = kind({ - name: 'ColorPicker', - - functional: true, - - propTypes: /** @lends sandstone/ColorPicker.ColorPickerBase.prototype */ { - /** - * Indicates the color. - * - * @type {String} - * @public - */ - color: PropTypes.string, - - /** - * Called when the color is modified. - * - * @type {Function} - * @public - */ - colorHandler: PropTypes.func, - - /** - * Customizes the component by mapping the supplied collection of CSS class names to the - * corresponding internal elements and states of this component. - * - * The following classes are supported: - * - * `colorPicker` - The root class name - * `coloredDiv` - A class name used for a single div - * - * @type {Object} - * @public - */ - css: PropTypes.object, - - /** - * Applies the `disabled` class. - * - * When `true`, the color picker is shown as disabled. - * - * @type {Boolean} - * @default false - * @public - */ - disabled: PropTypes.bool, - - /** - * Called to open or close the color picker. - * - * @type {Function} - * @public - */ - onTogglePopup: PropTypes.func, - - /** - * Indicates if the color picker is open. - * - * When `true`, contextual popup opens. - * - * @type {Boolean} - * @default false - * @private - */ - popupOpen: PropTypes.bool, - - /** - * Contains an array with a couple of possible preset colors. - * - * @type {Array} - * @public - */ - presetColors: PropTypes.array, - - /** - * Contains the text that shows near color picker. - * - * @type {String} - * @public - */ - text: PropTypes.string - }, - - handlers: { - handleClosePopup: (ev, {onTogglePopup}) => { - onTogglePopup(); - }, - handleOpenPopup: (ev, {disabled, onTogglePopup}) => { - if (!disabled) { - onTogglePopup(); - } +const ColorPickerBase = ({color = '#eb4034', colors = ['#eb4034', '#32a852', '#3455eb'], css, disabled, onChangeColor, open, type = 'grid', ...rest}) => { + const [favoriteColors, setFavoriteColors] = useState(colors); + const [selectedColor, setSelectedColor] = useState(color); + const [tabLayoutIndex, setTabLayoutIndex] = useState(0); + + useEffect(() => { + setFavoriteColors(colors); + setSelectedColor(color); + + switch (type) { + case 'grid': + setTabLayoutIndex(0); + return; + case 'spectrum': + setTabLayoutIndex(1); + return; + case 'sliders': + setTabLayoutIndex(2); + return; + default: + setTabLayoutIndex(0); } - }, - - styles: { - css: componentCss, - publicClassNames: true - }, - - render: ({color, colorHandler, css, disabled = false, handleClosePopup, handleOpenPopup, popupOpen = false, presetColors, text, ...rest}) => { - delete rest.onTogglePopup; - - const CloseIcon = useCallback((props) => , [css]); - const slotAfter = ; - - return ( - - - {text} - - - - - {text} - - -
); }; @@ -252,7 +251,7 @@ FavoriteColors.propTypes = { * @ui * @public */ -const ColorPickerBase = ({color = '#eb4034', colors = ['#eb4034', '#32a852', '#3455eb'], css, disabled, onChangeColor, open, type = 'grid', ...rest}) => { +const ColorPickerBase = ({color = '#eb4034', colors = ['#eb4034', '#32a852', '#3455eb'], disabled, onChangeColor, open, type = 'grid', ...rest}) => { const [favoriteColors, setFavoriteColors] = useState(colors); const [selectedColor, setSelectedColor] = useState(color); const [tabLayoutIndex, setTabLayoutIndex] = useState(0); @@ -298,38 +297,50 @@ const ColorPickerBase = ({color = '#eb4034', colors = ['#eb4034', '#32a852', '#3 setTabLayoutIndex(2); }, [disabled, setTabLayoutIndex]); + const renderContent = () => { + if (tabLayoutIndex === 0) { + return ( + + ); + } else if (tabLayoutIndex === 1) { + return ( + + ); + } else if (tabLayoutIndex === 2) { + return ( + + ); + } else { + return
Loading
; + } + }; + return ( - - -
- -
-
- -
- -
-
- -
- -
-
-
+ +
+ {renderContent()} +
- - - +
@@ -355,15 +366,6 @@ ColorPickerBase.propTypes = {/** @lends sandstone/ColorPicker.ColorPickerBase.pr */ colors: PropTypes.array, - /** - * Customizes the component by mapping the supplied collection of CSS class names to the - * corresponding internal elements and states of this component. - * - * @type {Object} - * @public - */ - css: PropTypes.object, - /** * Applies a disabled style and prevents interacting with the component. * diff --git a/ColorPicker/ColorPicker.module.less b/ColorPicker/ColorPicker.module.less index 343fa6678b..d8ee756e24 100644 --- a/ColorPicker/ColorPicker.module.less +++ b/ColorPicker/ColorPicker.module.less @@ -1,6 +1,7 @@ // ColorPicker.module.less // @import "../styles/mixins.less"; +@import "../styles/variables.less"; @keyframes shakeColor { 0% { transform: translateX(0) } @@ -10,47 +11,16 @@ 100% { transform: translateX(0) } } -.pickerTabLayout { - align-items: center; - overflow: hidden; - - > :first-child { - max-width: 100%; /* Fallback */ - max-width: -webkit-fill-available; /* WebKit-specific */ - max-width: stretch; /* Modern standard, for other browsers */ - } +.pickerTabGroup { + min-width: @sand-colorpicker-tabgroup-width; + padding-bottom: @sand-colorpicker-padding; } .colorPicker { display: flex; - height: 702px; + height: @sand-colorpicker-height; justify-content: center; - min-width: 800px; - padding: 0 21px; -} - -.selectedColorColumn { - margin-top: 15px; - - .selectedColor { - border-radius: 24px; - border: 9px solid; - height: 240px; - margin-inline: 0; - transition: transform .2s; - - .selectedColorIcon { - transition: transform .2s; - } - - .focus({ - box-shadow: 0 0 12px 6px inset; - - .selectedColorIcon { - transform: scale(1.2); - } - }); - } + padding: @sand-colorpicker-padding; } .favoriteColorsRow { @@ -58,7 +28,7 @@ .favoriteColor { border-radius: 50%; - border: 6px solid; + border: @sand-colorpicker-border-medium solid; transition: transform .2s; .deleteButton { @@ -77,3 +47,28 @@ animation-iteration-count: infinite; } } + +.selectedColorContainer { + justify-content: center; + margin-top: @sand-colorpicker-selectedcolor-margin; + + .selectedColor { + border-radius: @sand-colorpicker-selectedcolor-border-radius; + border: @sand-colorpicker-border-medium solid; + height: @sand-colorpicker-selectedcolor-height; + margin-inline: 0; + transition: transform .2s; + + .selectedColorIcon { + transition: transform .2s; + } + + .focus({ + box-shadow: 0 0 12px 6px inset; + + .selectedColorIcon { + transform: scale(1.2); + } + }); + } +} diff --git a/ColorPicker/ColorPickerGrid.module.less b/ColorPicker/ColorPickerGrid.module.less index 26fdf84bf0..8ff91059fb 100644 --- a/ColorPicker/ColorPickerGrid.module.less +++ b/ColorPicker/ColorPickerGrid.module.less @@ -8,9 +8,9 @@ display: flex; .colorBlock { - border: 9px solid transparent; - height: 48px; - width: 48px; + border: @sand-colorpicker-border-large solid @sand-colorpicker-colorblock-border-color; + height: @sand-colorpicker-grid-colorblock-size; + width: @sand-colorpicker-grid-colorblock-size; .focus({ border-color: var(--sand-colorpicker-grid-focus-border-color); diff --git a/ColorPicker/ColorPickerSlider.js b/ColorPicker/ColorPickerSlider.js index 9459b710e4..e08b1fbf35 100644 --- a/ColorPicker/ColorPickerSlider.js +++ b/ColorPicker/ColorPickerSlider.js @@ -115,7 +115,7 @@ const ColorPickerSliderRGB = ({disabled, selectedColor, selectedColorHandler, .. Red - + {localRed} Green - + {localGreen} Blue - + - + {['RGB', 'HSL']} diff --git a/ColorPicker/ColorPickerSlider.module.less b/ColorPicker/ColorPickerSlider.module.less index a16ccca386..d82809f09e 100644 --- a/ColorPicker/ColorPickerSlider.module.less +++ b/ColorPicker/ColorPickerSlider.module.less @@ -1,77 +1,74 @@ // ColorPickerSlider.module.less // +@import "../styles/colors.less"; @import "../styles/mixins.less"; +@import "../styles/variables.less"; .sliderPickerContainer { - margin-top: -63px; + .pickerSelect { + margin: 0; + height: @sand-colorpicker-dropdown-height; + width: @sand-colorpicker-input-width; - .containerRow { - padding-bottom: 30px; - } -} - -.slidersContainer { - padding: 0 30px; - - .labelText { - font-size: 42px; - margin: 9px 0; + [role*=button] { + height: @sand-colorpicker-dropdown-height; + } } - .sliderCell { - border-radius: 99px; + .hexInput { + width: @sand-colorpicker-input-width; } - .slider { - --sand-progress-bg-color-opacity: 0; - border-radius: 99px; - height: 33px; - margin: 0 18px; + .slidersContainer { + margin: @sand-colorpicker-margin-medium 0 0 @sand-colorpicker-margin-large; - .focus({ - .knob { - transform: scale(1.1); + .labelText { + font-size: @sand-colorpicker-slider-label-font-size; + margin: @sand-colorpicker-margin-small 0; + } - &::before { - background-color: transparent; - } - } - }); + .sliderRow { + justify-content: space-between; - .knob { - &::before { - --sand-slider-disabled-knob-bg-color: transparent; - border-radius: 99px; - height: 102px; - width: 102px; + .sliderCell { + border-radius: @sand-colorpicker-circle-border-radius; } - } - } - .outputText { - display: flex; - justify-content: center; - align-items: center; - background-color: #eeeeee; - color: rgb(76, 80, 89); - border-radius: 12px; - margin-left: 30px; - } -} + .slider { + --sand-progress-bg-color-opacity: 0; + border-radius: @sand-colorpicker-circle-border-radius; + margin: 0 @sand-colorpicker-margin-medium; + height: @sand-colorpicker-slider-height; -.pickerSelect { - margin: 0; - height: 129px; + .focus({ + .knob { + transform: scale(1.1); - [role*=button] { - height: 129px; - } -} + &::before { + background-color: var(--sand-progress-slider-color); + } + } + }); -.hexInput { - width: 402px; + .knob { + &::before { + --sand-slider-disabled-knob-bg-color: var(--sand-progress-slider-color); + border-radius: @sand-colorpicker-circle-border-radius; + height: @sand-colorpicker-slider-knob-size; + width: @sand-colorpicker-slider-knob-size; + } + } + } - input { - width: 372px; + .outputText { + display: flex; + justify-content: center; + align-items: center; + background-color: @sand-colorpicker-output-bg-color; + color: @sand-colorpicker-output-color; + border-radius: @sand-colorpicker-output-border-radius; + margin-left: @sand-colorpicker-margin-large; + } + } } } diff --git a/ColorPicker/ColorPickerSpectrum.module.less b/ColorPicker/ColorPickerSpectrum.module.less index 47fddea143..9d637a5429 100644 --- a/ColorPicker/ColorPickerSpectrum.module.less +++ b/ColorPicker/ColorPickerSpectrum.module.less @@ -9,20 +9,20 @@ .gradientCanvas { height: 100%; - touch-action: none; width: 100%; + touch-action: none; } -} -.circleIndicator { - border: 3px solid #808080; - height: 48px; - border-radius: 99px; - pointer-events: none; - position: absolute; - width: 48px; + .circleIndicator { + position: absolute; + width: @sand-colorpicker-spectrum-circleindicator-size; + height: @sand-colorpicker-spectrum-circleindicator-size; + border: @sand-colorpicker-border-small solid @sand-colorpicker-spectrum-circleindicator-color; + border-radius: @sand-colorpicker-circle-border-radius; + pointer-events: none; - .focus({ - border: 6px solid #000; - }) + .focus({ + border: @sand-colorpicker-border-medium solid @sand-colorpicker-spectrum-circleindicator-focus-color; + }) + } } diff --git a/ColorPicker/tests/ColorPicker-specs.js b/ColorPicker/tests/ColorPicker-specs.js index 59488f535e..86fbb9cc45 100644 --- a/ColorPicker/tests/ColorPicker-specs.js +++ b/ColorPicker/tests/ColorPicker-specs.js @@ -126,7 +126,7 @@ describe('ColorPicker', () => { /> ); const favoriteColors = container.querySelectorAll('.favoriteColor'); // eslint-disable-line testing-library/no-container - const selectedColor = container.querySelector('.selectedColorColumn'); // eslint-disable-line testing-library/no-container + const selectedColor = container.querySelector('.selectedColorContainer'); // eslint-disable-line testing-library/no-container expect(favoriteColors[0]).toBeInTheDocument(); expect(selectedColor).toBeInTheDocument(); diff --git a/styles/colors.less b/styles/colors.less index 16a72b2b40..1543d7316c 100644 --- a/styles/colors.less +++ b/styles/colors.less @@ -190,6 +190,13 @@ @sand-checkbox-indeterminate-focus-border-color: @sand-checkbox-focus-border-color; @sand-checkbox-standalone-bg-disabled-focus-bg-color: @sand-disabled-focus-bg-color; +// ColorPicker +@sand-colorpicker-colorblock-border-color: transparent; +@sand-colorpicker-output-bg-color: #eeeeee; +@sand-colorpicker-output-color: #4c5059; +@sand-colorpicker-spectrum-circleindicator-color: #808080; +@sand-colorpicker-spectrum-circleindicator-focus-color: #000000; + // ContextualPopup // --------------------------------------- @sand-contextualpopup-bg-color: @sand-overlay-bg-color; diff --git a/styles/variables.less b/styles/variables.less index e223c6ccd7..58a7ef79ae 100644 --- a/styles/variables.less +++ b/styles/variables.less @@ -400,6 +400,29 @@ @sand-checkbox-container-position: ((@sand-icon-tiny-size - 120px) / 2); // Container's total dimensions should be `120px` defined in the GUI spec @sand-checkbox-container-border-radius: @sand-item-border-radius; +// ColorPicker +@sand-colorpicker-border-large: 9px; +@sand-colorpicker-border-medium: 6px; +@sand-colorpicker-border-small: 3px; +@sand-colorpicker-circle-border-radius: 99px; +@sand-colorpicker-dropdown-height: 129px; +@sand-colorpicker-grid-colorblock-size: 54px; +@sand-colorpicker-height: 702px; +@sand-colorpicker-input-width: 402px; +@sand-colorpicker-margin-large: 30px; +@sand-colorpicker-margin-medium: 18px; +@sand-colorpicker-margin-small: 9px; +@sand-colorpicker-output-border-radius: 12px; +@sand-colorpicker-padding: 24px; +@sand-colorpicker-selectedcolor-border-radius: 24px; +@sand-colorpicker-selectedcolor-height: 240px; +@sand-colorpicker-selectedcolor-margin: 15px; +@sand-colorpicker-slider-height: 33px; +@sand-colorpicker-slider-knob-size: 102px; +@sand-colorpicker-slider-label-font-size: 42px; +@sand-colorpicker-spectrum-circleindicator-size: 48px; +@sand-colorpicker-tabgroup-width: 1200px; + // ContextualMenu // --------------------------------------- @sand-contextualmenu-container-padding: 36px 0; diff --git a/tests/ui/specs/ColorPicker/ColorPicker-specs.js b/tests/ui/specs/ColorPicker/ColorPicker-specs.js index c3f5bb04b6..006c0d7195 100644 --- a/tests/ui/specs/ColorPicker/ColorPicker-specs.js +++ b/tests/ui/specs/ColorPicker/ColorPicker-specs.js @@ -8,10 +8,6 @@ describe('ColorPicker', function () { const colorPicker = Page.components.colorPicker; describe('ColorPicker Grid', function () { - it('should focus Grid tab', async function () { - expect(await (await colorPicker.tabItems())[0].isFocused()).toBe(true); - }); - describe('5-way', function () { it('should change selectedColor with 5-way keys', async function () { // read background color of forth color block form first colors column @@ -52,10 +48,6 @@ describe('ColorPicker', function () { await browser.pause(500); }); - it('should focus Spectrum tab on 5-way Right', async function () { - expect(await (await colorPicker.tabItems())[1].isFocused()).toBe(true); - }); - describe('5-way', function () { it('should change selected color with 5-way keys', async function () { await Page.spotlightDown(); @@ -63,9 +55,10 @@ describe('ColorPicker', function () { for (let i = 0; i < 20; i++) { await Page.spotlightDown(); } - for (let i = 0; i < 17; i++) { + for (let i = 0; i < 19; i++) { await Page.spotlightRight(); } + await browser.pause(500); const spectrumIndicatorStyle = await colorPicker.spectrumIndicator.getAttribute('style'); const spectrumIndicator = String(spectrumIndicatorStyle.split(';').filter(attr => attr.includes('background-color'))).trim(); @@ -78,7 +71,7 @@ describe('ColorPicker', function () { describe('pointer', function () { it('should change color when clicking on the canvas', async function () { - await (await colorPicker.tabItems())[1].click(); + await (await colorPicker.tabgroupTabs())[1].click(); const canvas = await colorPicker.canvas; await canvas.click(); @@ -102,10 +95,6 @@ describe('ColorPicker', function () { await browser.pause(500); }); - it('should focus Sliders tab on 5-way Right', async function () { - expect(await (await colorPicker.tabItems())[2].isFocused()).toBe(true); - }); - describe('RGB Color Picker', function () { it('should display Red/Green/Blue labels', async function () { const firstSliderLabel = await (await colorPicker.slidersLabel())[0].getText(); diff --git a/tests/ui/specs/ColorPicker/ColorPickerPage.js b/tests/ui/specs/ColorPicker/ColorPickerPage.js index 3ccd9d6331..a04273e172 100644 --- a/tests/ui/specs/ColorPicker/ColorPickerPage.js +++ b/tests/ui/specs/ColorPicker/ColorPickerPage.js @@ -1,7 +1,6 @@ 'use strict'; const {element, getComponent, Page} = require('@enact/ui-test-utils/utils'); -const getTabs = getComponent({component: 'TabLayout', child: 'tabs'}); const colorPicker = getComponent({component: 'ColorPicker', child: 'colorPicker'}); class ColorPickerInterface { @@ -14,12 +13,8 @@ class ColorPickerInterface { return browser.$(this.selector); } - async tabItems () { - return await (await this.tabs()).$$('.Button_Button_button'); - } - - async tabs () { - return await getTabs(this.self); + async tabgroupTabs () { + return await $$('.TabLayout_TabGroup_tab'); } async colorBlock () { From 423826cffb0793cdb25a2f897d5749e4c7150135 Mon Sep 17 00:00:00 2001 From: Adrian Cocoara Date: Fri, 17 Jan 2025 11:58:28 +0200 Subject: [PATCH 20/24] deleted unnecessary else block --- ColorPicker/ColorPicker.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/ColorPicker/ColorPicker.js b/ColorPicker/ColorPicker.js index 326f14219d..84be69e828 100644 --- a/ColorPicker/ColorPicker.js +++ b/ColorPicker/ColorPicker.js @@ -310,8 +310,6 @@ const ColorPickerBase = ({color = '#eb4034', colors = ['#eb4034', '#32a852', '#3 return ( ); - } else { - return
Loading
; } }; From 5c52781ba46b67e4ec16c6727133dcdf94b62a55 Mon Sep 17 00:00:00 2001 From: Adrian Cocoara Date: Fri, 17 Jan 2025 14:29:35 +0200 Subject: [PATCH 21/24] fix isomorphic tests --- ColorPicker/ColorPickerSlider.js | 3 ++- ColorPicker/utils.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ColorPicker/ColorPickerSlider.js b/ColorPicker/ColorPickerSlider.js index e08b1fbf35..f30cf9a460 100644 --- a/ColorPicker/ColorPickerSlider.js +++ b/ColorPicker/ColorPickerSlider.js @@ -440,6 +440,7 @@ const ColorPickerSlider = ({disabled, selectedColor, selectedColorHandler, type setDropdownValue(1); } }, [setDropdownValue, setPickerType]); + const inputValue = selectedColor ? selectedColor.toUpperCase() : '#000000' return ( @@ -467,7 +468,7 @@ const ColorPickerSlider = ({disabled, selectedColor, selectedColorHandler, type onBlur={handleBlur} onChange={handleInputChange} spotlightDisabled={disabled} - value={selectedColor.toUpperCase()} + value={inputValue} />
diff --git a/ColorPicker/utils.js b/ColorPicker/utils.js index 29341f21d3..c7ce0375ee 100644 --- a/ColorPicker/utils.js +++ b/ColorPicker/utils.js @@ -112,7 +112,7 @@ const getHexColorFromGradient = (canvasRef, x, y) => { * @private */ const generateOppositeColor = (hexColor) => { - hexColor = hexColor.replace('#', ''); + hexColor = hexColor !== undefined ? hexColor.replace('#', '') : '000000'; const bigint = parseInt(hexColor, 16); const r = (bigint >> 16) & 255; From 517d66eb15c0a866cc2fbfd873f332d494218486 Mon Sep 17 00:00:00 2001 From: Adrian Cocoara Date: Fri, 17 Jan 2025 15:11:50 +0200 Subject: [PATCH 22/24] fix isomorphic slice and fix layout --- ColorPicker/ColorPickerSlider.js | 6 +++--- ColorPicker/utils.js | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ColorPicker/ColorPickerSlider.js b/ColorPicker/ColorPickerSlider.js index f30cf9a460..856b422a67 100644 --- a/ColorPicker/ColorPickerSlider.js +++ b/ColorPicker/ColorPickerSlider.js @@ -276,7 +276,7 @@ const ColorPickerSliderHSL = ({disabled, selectedColor, selectedColorHandler, .. Hue - + Saturation - + Lightness - + { * @private */ const generateOppositeColor = (hexColor) => { - hexColor = hexColor !== undefined ? hexColor.replace('#', '') : '000000'; + hexColor = hexColor.replace('#', ''); const bigint = parseInt(hexColor, 16); const r = (bigint >> 16) & 255; @@ -134,9 +134,9 @@ const generateOppositeColor = (hexColor) => { */ const hexToHSL = (hexColor) => { // Convert hex to RGB first - const r = parseInt(hexColor.slice(1, 3), 16) / 255; - const g = parseInt(hexColor.slice(3, 5), 16) / 255; - const b = parseInt(hexColor.slice(5), 16) / 255; + const r = parseInt(hexColor?.slice(1, 3), 16) / 255; + const g = parseInt(hexColor?.slice(3, 5), 16) / 255; + const b = parseInt(hexColor?.slice(5), 16) / 255; // Calculate cmin, cmax, and delta const cmin = Math.min(r, g, b); @@ -183,7 +183,7 @@ const hexToHSL = (hexColor) => { * @private */ const hexToRGB = (hexColor) => { - let internalColor = hexColor.replace('#', '').split(''); + let internalColor = hexColor ? hexColor.replace('#', '').split('') : '000000'; return { red: parseInt(internalColor[0] + internalColor[1], 16), From b86d69ccd49000892feb5a65376816d7a4140155 Mon Sep 17 00:00:00 2001 From: Adrian Cocoara Date: Fri, 17 Jan 2025 15:26:44 +0200 Subject: [PATCH 23/24] fix isomorphic part 2 --- ColorPicker/ColorPickerSlider.js | 3 +-- ColorPicker/utils.js | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/ColorPicker/ColorPickerSlider.js b/ColorPicker/ColorPickerSlider.js index 856b422a67..5349c3799e 100644 --- a/ColorPicker/ColorPickerSlider.js +++ b/ColorPicker/ColorPickerSlider.js @@ -440,7 +440,6 @@ const ColorPickerSlider = ({disabled, selectedColor, selectedColorHandler, type setDropdownValue(1); } }, [setDropdownValue, setPickerType]); - const inputValue = selectedColor ? selectedColor.toUpperCase() : '#000000' return ( @@ -468,7 +467,7 @@ const ColorPickerSlider = ({disabled, selectedColor, selectedColorHandler, type onBlur={handleBlur} onChange={handleInputChange} spotlightDisabled={disabled} - value={inputValue} + value={selectedColor?.toUpperCase()} /> diff --git a/ColorPicker/utils.js b/ColorPicker/utils.js index 56f3aa1358..85a57fd269 100644 --- a/ColorPicker/utils.js +++ b/ColorPicker/utils.js @@ -132,11 +132,11 @@ const generateOppositeColor = (hexColor) => { * @returns {{h: number, s: number, l: number}} HSL values * @private */ -const hexToHSL = (hexColor) => { +const hexToHSL = (hexColor = '#000000') => { // Convert hex to RGB first - const r = parseInt(hexColor?.slice(1, 3), 16) / 255; - const g = parseInt(hexColor?.slice(3, 5), 16) / 255; - const b = parseInt(hexColor?.slice(5), 16) / 255; + const r = parseInt(hexColor.slice(1, 3), 16) / 255; + const g = parseInt(hexColor.slice(3, 5), 16) / 255; + const b = parseInt(hexColor.slice(5), 16) / 255; // Calculate cmin, cmax, and delta const cmin = Math.min(r, g, b); @@ -182,8 +182,8 @@ const hexToHSL = (hexColor) => { * @returns {{red: number, green: number, blue: number}} RGB values * @private */ -const hexToRGB = (hexColor) => { - let internalColor = hexColor ? hexColor.replace('#', '').split('') : '000000'; +const hexToRGB = (hexColor = '000000') => { + let internalColor = hexColor.replace('#', '').split(''); return { red: parseInt(internalColor[0] + internalColor[1], 16), From 34cf6605faddc5ad44ac249c168e043acc4d1534 Mon Sep 17 00:00:00 2001 From: Adrian Cocoara Date: Fri, 17 Jan 2025 20:41:55 +0200 Subject: [PATCH 24/24] removed JSDoc example --- ColorPicker/ColorPicker.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ColorPicker/ColorPicker.js b/ColorPicker/ColorPicker.js index 84be69e828..03a67e88f5 100644 --- a/ColorPicker/ColorPicker.js +++ b/ColorPicker/ColorPicker.js @@ -2,12 +2,6 @@ * Sandstone component that allows the user to choose a color * either from a grid, a spectrum, or RGB/HSL color sliders. * - * @example - * console.log} - * open - * /> - * * @module sandstone/ColorPicker * @exports ColorPicker * @exports ColorPickerBase