From 4a041455f99b91c353020bb1db94d34a78504033 Mon Sep 17 00:00:00 2001 From: Levko Kravets Date: Thu, 12 Sep 2019 14:39:52 +0300 Subject: [PATCH 1/6] Color picker component --- client/app/components/ColorBox.jsx | 19 +-- client/app/components/ColorPicker/Panel.jsx | 111 ++++++++++++++++++ client/app/components/ColorPicker/Swatch.jsx | 26 ++++ client/app/components/ColorPicker/index.jsx | 86 ++++++++++++++ client/app/components/ColorPicker/index.less | 31 +++++ client/app/components/ColorPicker/panel.less | 28 +++++ client/app/components/ColorPicker/swatch.less | 30 +++++ client/app/components/color-box.less | 19 +-- package.json | 1 + 9 files changed, 320 insertions(+), 31 deletions(-) create mode 100644 client/app/components/ColorPicker/Panel.jsx create mode 100644 client/app/components/ColorPicker/Swatch.jsx create mode 100644 client/app/components/ColorPicker/index.jsx create mode 100644 client/app/components/ColorPicker/index.less create mode 100644 client/app/components/ColorPicker/panel.less create mode 100644 client/app/components/ColorPicker/swatch.less diff --git a/client/app/components/ColorBox.jsx b/client/app/components/ColorBox.jsx index 73b3f3681e..dd83c3f49a 100644 --- a/client/app/components/ColorBox.jsx +++ b/client/app/components/ColorBox.jsx @@ -1,23 +1,12 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +// ANGULAR_REMOVE_ME import { react2angular } from 'react2angular'; -import './color-box.less'; - -export function ColorBox({ color }) { - return ; -} +import ColorPicker from '@/components/ColorPicker'; -ColorBox.propTypes = { - color: PropTypes.string, -}; - -ColorBox.defaultProps = { - color: 'transparent', -}; +import './color-box.less'; export default function init(ngModule) { - ngModule.component('colorBox', react2angular(ColorBox)); + ngModule.component('colorBox', react2angular(ColorPicker.Swatch)); } init.init = true; diff --git a/client/app/components/ColorPicker/Panel.jsx b/client/app/components/ColorPicker/Panel.jsx new file mode 100644 index 0000000000..898b848c1f --- /dev/null +++ b/client/app/components/ColorPicker/Panel.jsx @@ -0,0 +1,111 @@ +import { chunk, map, toString } from 'lodash'; +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import tinycolor from 'tinycolor2'; +import Card from 'antd/lib/card'; +import Icon from 'antd/lib/icon'; +import Tooltip from 'antd/lib/tooltip'; +import Input from 'antd/lib/input'; +import Typography from 'antd/lib/typography'; +import Swatch from './Swatch'; + +import './panel.less'; + +export default function Panel({ + color, presetColors, presetColumns, interactive, visible, onChange, onApply, onCancel, +}) { + const [selectedColor, setSelectedColor] = useState(color); + const [inputValue, setInputValue] = useState(color); + + function updateSelectedColor(value, isUserInput = false) { + if (isUserInput) { + setInputValue(value); + } + value = tinycolor(value); + if (value.isValid()) { + value = '#' + value.toHex().toUpperCase(); + setSelectedColor(value); + if (!isUserInput) { + setInputValue(toString(value).toUpperCase().substr(1)); + } + if (value !== selectedColor) { + onChange(value); + if (interactive) { + onApply(value, false); + } + } + } + } + + useEffect(() => { + if (visible) { + updateSelectedColor(color); + } + }, [color, visible]); + + const actions = []; + if (!interactive) { + actions.push(( + + { updateSelectedColor(color); onCancel(color, true); }} /> + + )); + actions.push(( + + onApply(selectedColor, true)} /> + + )); + } + + presetColors = chunk(presetColors, presetColumns); + + return ( + + {map(presetColors, (group, index) => ( +
+ {map(group, c => updateSelectedColor(c)} />)} +
+ ))} +
+ #} + value={inputValue} + onChange={e => updateSelectedColor(e.target.value, true)} + onBlur={() => updateSelectedColor(selectedColor)} + onPressEnter={() => onApply(selectedColor, true)} + /> +
+
+ ); +} + +Panel.propTypes = { + color: PropTypes.string, + presetColors: PropTypes.arrayOf(PropTypes.string), + presetColumns: PropTypes.number, + visible: PropTypes.bool, + interactive: PropTypes.bool, + onChange: PropTypes.func, + onApply: PropTypes.func, + onCancel: PropTypes.func, +}; + +Panel.defaultProps = { + color: '#FFFFFF', + presetColors: null, + presetColumns: 8, + visible: false, + interactive: false, + onChange: () => {}, + onApply: () => {}, + onCancel: () => {}, +}; diff --git a/client/app/components/ColorPicker/Swatch.jsx b/client/app/components/ColorPicker/Swatch.jsx new file mode 100644 index 0000000000..e23e5d8ead --- /dev/null +++ b/client/app/components/ColorPicker/Swatch.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import './swatch.less'; + +export default function Swatch({ className, color, size, ...props }) { + return ( + + ); +} + +Swatch.propTypes = { + className: PropTypes.string, + color: PropTypes.string, + size: PropTypes.number, +}; + +Swatch.defaultProps = { + className: '', + color: 'transparent', + size: 12, +}; diff --git a/client/app/components/ColorPicker/index.jsx b/client/app/components/ColorPicker/index.jsx new file mode 100644 index 0000000000..8fdecf89ad --- /dev/null +++ b/client/app/components/ColorPicker/index.jsx @@ -0,0 +1,86 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; +import Popover from 'antd/lib/popover'; + +import Panel from './Panel'; +import Swatch from './Swatch'; + +import './index.less'; + +export default function ColorPicker({ + color, placement, presetColors, presetColumns, triggerSize, interactive, children, onChange, +}) { + const [visible, setVisible] = useState(false); + const [currentColor, setCurrentColor] = useState(color); + + function handleApply(selectedColor, shouldClose = true) { + onChange(selectedColor); + if (shouldClose) { + setVisible(false); + } + } + + function handleCancel(unused, shouldClose = true) { + if (shouldClose) { + setVisible(false); + } + } + + return ( + + )} + trigger="click" + placement={placement} + visible={visible} + onVisibleChange={setVisible} + > + {children || ()} + + ); +} + +ColorPicker.propTypes = { + color: PropTypes.string, + placement: PropTypes.oneOf([ + 'top', 'left', 'right', 'bottom', + 'topLeft', 'topRight', 'bottomLeft', 'bottomRight', + 'leftTop', 'leftBottom', 'rightTop', 'rightBottom', + ]), + presetColors: PropTypes.arrayOf(PropTypes.string), + presetColumns: PropTypes.number, + triggerSize: PropTypes.number, + interactive: PropTypes.bool, + children: PropTypes.node, + onChange: PropTypes.func, +}; + +ColorPicker.defaultProps = { + color: '#FFFFFF', + placement: 'top', + presetColors: null, + presetColumns: 8, + triggerSize: 30, + interactive: false, + children: null, + onChange: () => {}, +}; + +ColorPicker.Panel = Panel; +ColorPicker.Swatch = Swatch; diff --git a/client/app/components/ColorPicker/index.less b/client/app/components/ColorPicker/index.less new file mode 100644 index 0000000000..ed09fd41a4 --- /dev/null +++ b/client/app/components/ColorPicker/index.less @@ -0,0 +1,31 @@ +.color-picker { + &.color-picker-with-actions { + &.ant-popover-placement-top, + &.ant-popover-placement-topLeft, + &.ant-popover-placement-topRight, + &.ant-popover-placement-leftBottom, + &.ant-popover-placement-rightBottom { + > .ant-popover-content > .ant-popover-arrow { + border-color: #fafafa; // same as card actions + } + } + } + + &.ant-popover-placement-bottom, + &.ant-popover-placement-bottomLeft, + &.ant-popover-placement-bottomRight, + &.ant-popover-placement-leftTop, + &.ant-popover-placement-rightTop { + > .ant-popover-content > .ant-popover-arrow { + border-color: var(--color-picker-selected-color); + } + } + + .ant-popover-inner-content { + padding: 0; + } +} + +.color-picker-trigger { + cursor: pointer; +} diff --git a/client/app/components/ColorPicker/panel.less b/client/app/components/ColorPicker/panel.less new file mode 100644 index 0000000000..ae0dc85380 --- /dev/null +++ b/client/app/components/ColorPicker/panel.less @@ -0,0 +1,28 @@ +.color-picker-panel { + .ant-card-head { + text-align: center; + border-bottom-color: rgba(0, 0, 0, 0.1); + } + + .ant-card-body { + padding: 10px; + } + + .color-picker-panel-block { + margin: 0 0 10px 0; + white-space: nowrap; + + &:last-child { + margin-bottom: 0; + } + + .color-swatch { + cursor: pointer; + margin: 0 10px 0 0; + + &:last-child { + margin-right: 0; + } + } + } +} diff --git a/client/app/components/ColorPicker/swatch.less b/client/app/components/ColorPicker/swatch.less new file mode 100644 index 0000000000..4dea312c44 --- /dev/null +++ b/client/app/components/ColorPicker/swatch.less @@ -0,0 +1,30 @@ +.color-swatch { + display: inline-block; + box-sizing: border-box; + vertical-align: middle; + border-radius: 2px; + overflow: hidden; + width: 12px; + + @cell-size: 12px; + @cell-color: rgba(0, 0, 0, 0.1); + + background-color: transparent; + background-image: + linear-gradient(45deg, @cell-color 25%, transparent 25%), + linear-gradient(-45deg, @cell-color 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, @cell-color 75%), + linear-gradient(-45deg, transparent 75%, @cell-color 75%); + background-size: @cell-size @cell-size; + background-position: 0 0, 0 @cell-size/2, @cell-size/2 -@cell-size/2, -@cell-size/2 0px; + + &:before { + content: ""; + display: block; + padding-top: ~"calc(100% - 2px)"; + background-color: inherit; + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 2px; + overflow: hidden; + } +} diff --git a/client/app/components/color-box.less b/client/app/components/color-box.less index 7a23fd1308..4684f0c193 100644 --- a/client/app/components/color-box.less +++ b/client/app/components/color-box.less @@ -1,19 +1,6 @@ -@import '../assets/less/inc/variables'; - +// ANGULAR_REMOVE_ME color-box { vertical-align: text-bottom; - display: inline; - - span { - width: 12px !important; - height: 12px !important; - display: inline-block !important; - margin-right: 5px; - vertical-align: middle; - border: 1px solid rgba(0,0,0,0.1); - } - & ~ span { - vertical-align: bottom; - color: @input-color; - } + display: inline-block; + margin-right: 5px; } diff --git a/package.json b/package.json index c30a8f3b5c..eb01a227bb 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "react-grid-layout": "git+https://github.com/getredash/react-grid-layout.git", "react-sortable-hoc": "^1.9.1", "react2angular": "^3.2.1", + "tinycolor2": "^1.4.1", "ui-select": "^0.19.8" }, "devDependencies": { From 3ebad03b356c37ba605a01fbc618d3a224864a26 Mon Sep 17 00:00:00 2001 From: Levko Kravets Date: Thu, 12 Sep 2019 22:53:36 +0300 Subject: [PATCH 2/6] Refine code + better component splitting --- client/app/components/ColorPicker/Input.jsx | 70 ++++++++++++ client/app/components/ColorPicker/Panel.jsx | 111 ------------------- client/app/components/ColorPicker/index.jsx | 96 +++++++++++----- client/app/components/ColorPicker/index.less | 9 ++ client/app/components/ColorPicker/input.less | 19 ++++ client/app/components/ColorPicker/panel.less | 28 ----- 6 files changed, 167 insertions(+), 166 deletions(-) create mode 100644 client/app/components/ColorPicker/Input.jsx delete mode 100644 client/app/components/ColorPicker/Panel.jsx create mode 100644 client/app/components/ColorPicker/input.less delete mode 100644 client/app/components/ColorPicker/panel.less diff --git a/client/app/components/ColorPicker/Input.jsx b/client/app/components/ColorPicker/Input.jsx new file mode 100644 index 0000000000..90a0d852a1 --- /dev/null +++ b/client/app/components/ColorPicker/Input.jsx @@ -0,0 +1,70 @@ +import { chunk, map } from 'lodash'; +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import tinycolor from 'tinycolor2'; +import TextInput from 'antd/lib/input'; +import Typography from 'antd/lib/typography'; +import Swatch from './Swatch'; + +import './input.less'; + +function validateColor(value, callback, prefix = '#') { + value = tinycolor(value); + if (value.isValid()) { + callback(prefix + value.toHex().toUpperCase()); + } +} + +export default function Input({ color, presetColors, presetColumns, onChange, onPressEnter }) { + const [inputValue, setInputValue] = useState(''); + const [isInputFocused, setIsInputFocused] = useState(false); + + presetColors = chunk(presetColors, presetColumns); + + function handleInputChange(value) { + setInputValue(value); + validateColor(value, onChange); + } + + useEffect(() => { + if (!isInputFocused) { + validateColor(color, setInputValue, ''); + } + }, [color, isInputFocused]); + + return ( + + {map(presetColors, (group, index) => ( +
+ {map(group, c => validateColor(c, onChange)} />)} +
+ ))} +
+ #} + value={inputValue} + onChange={e => handleInputChange(e.target.value)} + onFocus={() => setIsInputFocused(true)} + onBlur={() => setIsInputFocused(false)} + onPressEnter={onPressEnter} + /> +
+
+ ); +} + +Input.propTypes = { + color: PropTypes.string, + presetColors: PropTypes.arrayOf(PropTypes.string), + presetColumns: PropTypes.number, + onChange: PropTypes.func, + onPressEnter: PropTypes.func, +}; + +Input.defaultProps = { + color: '#FFFFFF', + presetColors: null, + presetColumns: 8, + onChange: () => {}, + onPressEnter: () => {}, +}; diff --git a/client/app/components/ColorPicker/Panel.jsx b/client/app/components/ColorPicker/Panel.jsx deleted file mode 100644 index 898b848c1f..0000000000 --- a/client/app/components/ColorPicker/Panel.jsx +++ /dev/null @@ -1,111 +0,0 @@ -import { chunk, map, toString } from 'lodash'; -import React, { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import tinycolor from 'tinycolor2'; -import Card from 'antd/lib/card'; -import Icon from 'antd/lib/icon'; -import Tooltip from 'antd/lib/tooltip'; -import Input from 'antd/lib/input'; -import Typography from 'antd/lib/typography'; -import Swatch from './Swatch'; - -import './panel.less'; - -export default function Panel({ - color, presetColors, presetColumns, interactive, visible, onChange, onApply, onCancel, -}) { - const [selectedColor, setSelectedColor] = useState(color); - const [inputValue, setInputValue] = useState(color); - - function updateSelectedColor(value, isUserInput = false) { - if (isUserInput) { - setInputValue(value); - } - value = tinycolor(value); - if (value.isValid()) { - value = '#' + value.toHex().toUpperCase(); - setSelectedColor(value); - if (!isUserInput) { - setInputValue(toString(value).toUpperCase().substr(1)); - } - if (value !== selectedColor) { - onChange(value); - if (interactive) { - onApply(value, false); - } - } - } - } - - useEffect(() => { - if (visible) { - updateSelectedColor(color); - } - }, [color, visible]); - - const actions = []; - if (!interactive) { - actions.push(( - - { updateSelectedColor(color); onCancel(color, true); }} /> - - )); - actions.push(( - - onApply(selectedColor, true)} /> - - )); - } - - presetColors = chunk(presetColors, presetColumns); - - return ( - - {map(presetColors, (group, index) => ( -
- {map(group, c => updateSelectedColor(c)} />)} -
- ))} -
- #} - value={inputValue} - onChange={e => updateSelectedColor(e.target.value, true)} - onBlur={() => updateSelectedColor(selectedColor)} - onPressEnter={() => onApply(selectedColor, true)} - /> -
-
- ); -} - -Panel.propTypes = { - color: PropTypes.string, - presetColors: PropTypes.arrayOf(PropTypes.string), - presetColumns: PropTypes.number, - visible: PropTypes.bool, - interactive: PropTypes.bool, - onChange: PropTypes.func, - onApply: PropTypes.func, - onCancel: PropTypes.func, -}; - -Panel.defaultProps = { - color: '#FFFFFF', - presetColors: null, - presetColumns: 8, - visible: false, - interactive: false, - onChange: () => {}, - onApply: () => {}, - onCancel: () => {}, -}; diff --git a/client/app/components/ColorPicker/index.jsx b/client/app/components/ColorPicker/index.jsx index 8fdecf89ad..ae3b0a1171 100644 --- a/client/app/components/ColorPicker/index.jsx +++ b/client/app/components/ColorPicker/index.jsx @@ -1,57 +1,99 @@ -import React, { useState } from 'react'; +import { toString } from 'lodash'; +import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; -import cx from 'classnames'; +import tinycolor from 'tinycolor2'; import Popover from 'antd/lib/popover'; +import Card from 'antd/lib/card'; +import Tooltip from 'antd/lib/tooltip'; +import Icon from 'antd/lib/icon'; -import Panel from './Panel'; +import ColorInput from './Input'; import Swatch from './Swatch'; import './index.less'; +function getPreviewColor(value, fallback = '') { + value = tinycolor(value); + return value.isValid() ? '#' + value.toHex().toUpperCase() : fallback; +} + export default function ColorPicker({ color, placement, presetColors, presetColumns, triggerSize, interactive, children, onChange, }) { const [visible, setVisible] = useState(false); - const [currentColor, setCurrentColor] = useState(color); + const [currentColor, setCurrentColor] = useState('#FFFFFF'); - function handleApply(selectedColor, shouldClose = true) { - onChange(selectedColor); - if (shouldClose) { - setVisible(false); + function handleApply() { + setVisible(false); + if (!interactive) { + onChange(currentColor); } } - function handleCancel(unused, shouldClose = true) { - if (shouldClose) { - setVisible(false); + function handleCancel() { + setVisible(false); + } + + const actions = []; + if (!interactive) { + actions.push(( + + + + )); + actions.push(( + + + + )); + } + + function handleInputChange(newColor) { + setCurrentColor(newColor); + if (interactive) { + onChange(newColor); } } + useEffect(() => { + if (visible) { + const value = tinycolor(color); + if (value.isValid()) { + setCurrentColor('#' + value.toHex().toUpperCase()); + } + } + }, [color, visible]); + return ( + + + )} trigger="click" placement={placement} visible={visible} onVisibleChange={setVisible} > - {children || ()} + {children || ()} ); } @@ -82,5 +124,5 @@ ColorPicker.defaultProps = { onChange: () => {}, }; -ColorPicker.Panel = Panel; +ColorPicker.Input = ColorInput; ColorPicker.Swatch = Swatch; diff --git a/client/app/components/ColorPicker/index.less b/client/app/components/ColorPicker/index.less index ed09fd41a4..00bf5768e2 100644 --- a/client/app/components/ColorPicker/index.less +++ b/client/app/components/ColorPicker/index.less @@ -24,6 +24,15 @@ .ant-popover-inner-content { padding: 0; } + + .ant-card-head { + text-align: center; + border-bottom-color: rgba(0, 0, 0, 0.1); + } + + .ant-card-body { + padding: 10px; + } } .color-picker-trigger { diff --git a/client/app/components/ColorPicker/input.less b/client/app/components/ColorPicker/input.less new file mode 100644 index 0000000000..56f9d7ec58 --- /dev/null +++ b/client/app/components/ColorPicker/input.less @@ -0,0 +1,19 @@ +.color-picker-input-swatches { + margin: 0 0 10px 0; + text-align: left; + white-space: nowrap; + + .color-swatch { + cursor: pointer; + margin: 0 10px 0 0; + + &:last-child { + margin-right: 0; + } + } +} + +.color-picker-input { + text-align: left; + white-space: nowrap; +} diff --git a/client/app/components/ColorPicker/panel.less b/client/app/components/ColorPicker/panel.less deleted file mode 100644 index ae0dc85380..0000000000 --- a/client/app/components/ColorPicker/panel.less +++ /dev/null @@ -1,28 +0,0 @@ -.color-picker-panel { - .ant-card-head { - text-align: center; - border-bottom-color: rgba(0, 0, 0, 0.1); - } - - .ant-card-body { - padding: 10px; - } - - .color-picker-panel-block { - margin: 0 0 10px 0; - white-space: nowrap; - - &:last-child { - margin-bottom: 0; - } - - .color-swatch { - cursor: pointer; - margin: 0 10px 0 0; - - &:last-child { - margin-right: 0; - } - } - } -} From a254501aee1f19d3863021d101f2e12a5bcd32c4 Mon Sep 17 00:00:00 2001 From: Levko Kravets Date: Thu, 12 Sep 2019 23:18:56 +0300 Subject: [PATCH 3/6] Allow empty values --- client/app/components/ColorPicker/Input.jsx | 26 +++++++++++++++++---- client/app/components/ColorPicker/index.jsx | 16 ++++++------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/client/app/components/ColorPicker/Input.jsx b/client/app/components/ColorPicker/Input.jsx index 90a0d852a1..deb5931889 100644 --- a/client/app/components/ColorPicker/Input.jsx +++ b/client/app/components/ColorPicker/Input.jsx @@ -1,14 +1,23 @@ -import { chunk, map } from 'lodash'; +import { isNil, isArray, chunk, map, toPairs, toString } from 'lodash'; import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import tinycolor from 'tinycolor2'; import TextInput from 'antd/lib/input'; import Typography from 'antd/lib/typography'; +import Tooltip from 'antd/lib/tooltip'; import Swatch from './Swatch'; import './input.less'; +function preparePresets(presetColors, presetColumns) { + presetColors = isArray(presetColors) ? map(presetColors, v => [toString(v), v]) : toPairs(presetColors); + return chunk(presetColors, presetColumns); +} + function validateColor(value, callback, prefix = '#') { + if (isNil(value)) { + callback(null); + } value = tinycolor(value); if (value.isValid()) { callback(prefix + value.toHex().toUpperCase()); @@ -19,7 +28,7 @@ export default function Input({ color, presetColors, presetColumns, onChange, on const [inputValue, setInputValue] = useState(''); const [isInputFocused, setIsInputFocused] = useState(false); - presetColors = chunk(presetColors, presetColumns); + const presets = preparePresets(presetColors, presetColumns); function handleInputChange(value) { setInputValue(value); @@ -34,9 +43,13 @@ export default function Input({ color, presetColors, presetColumns, onChange, on return ( - {map(presetColors, (group, index) => ( + {map(presets, (group, index) => (
- {map(group, c => validateColor(c, onChange)} />)} + {map(group, ([title, value]) => ( + + validateColor(value, onChange)} /> + + ))}
))}
@@ -55,7 +68,10 @@ export default function Input({ color, presetColors, presetColumns, onChange, on Input.propTypes = { color: PropTypes.string, - presetColors: PropTypes.arrayOf(PropTypes.string), + presetColors: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string), + PropTypes.objectOf(PropTypes.string), + ]), presetColumns: PropTypes.number, onChange: PropTypes.func, onPressEnter: PropTypes.func, diff --git a/client/app/components/ColorPicker/index.jsx b/client/app/components/ColorPicker/index.jsx index ae3b0a1171..c64497bd33 100644 --- a/client/app/components/ColorPicker/index.jsx +++ b/client/app/components/ColorPicker/index.jsx @@ -12,7 +12,7 @@ import Swatch from './Swatch'; import './index.less'; -function getPreviewColor(value, fallback = '') { +function validateColor(value, fallback = null) { value = tinycolor(value); return value.isValid() ? '#' + value.toHex().toUpperCase() : fallback; } @@ -21,7 +21,7 @@ export default function ColorPicker({ color, placement, presetColors, presetColumns, triggerSize, interactive, children, onChange, }) { const [visible, setVisible] = useState(false); - const [currentColor, setCurrentColor] = useState('#FFFFFF'); + const [currentColor, setCurrentColor] = useState(''); function handleApply() { setVisible(false); @@ -57,10 +57,7 @@ export default function ColorPicker({ useEffect(() => { if (visible) { - const value = tinycolor(color); - if (value.isValid()) { - setCurrentColor('#' + value.toHex().toUpperCase()); - } + setCurrentColor(validateColor(color)); } }, [color, visible]); @@ -93,7 +90,7 @@ export default function ColorPicker({ visible={visible} onVisibleChange={setVisible} > - {children || ()} + {children || ()} ); } @@ -105,7 +102,10 @@ ColorPicker.propTypes = { 'topLeft', 'topRight', 'bottomLeft', 'bottomRight', 'leftTop', 'leftBottom', 'rightTop', 'rightBottom', ]), - presetColors: PropTypes.arrayOf(PropTypes.string), + presetColors: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string), + PropTypes.objectOf(PropTypes.string), + ]), presetColumns: PropTypes.number, triggerSize: PropTypes.number, interactive: PropTypes.bool, From f93bb9b5f9ffd90282875f870da8621bb88743c3 Mon Sep 17 00:00:00 2001 From: Levko Kravets Date: Thu, 12 Sep 2019 23:24:58 +0300 Subject: [PATCH 4/6] Add title (tooltip) support to Swatch component --- client/app/components/ColorPicker/Input.jsx | 5 +---- client/app/components/ColorPicker/Swatch.jsx | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/client/app/components/ColorPicker/Input.jsx b/client/app/components/ColorPicker/Input.jsx index deb5931889..5128cfa181 100644 --- a/client/app/components/ColorPicker/Input.jsx +++ b/client/app/components/ColorPicker/Input.jsx @@ -4,7 +4,6 @@ import PropTypes from 'prop-types'; import tinycolor from 'tinycolor2'; import TextInput from 'antd/lib/input'; import Typography from 'antd/lib/typography'; -import Tooltip from 'antd/lib/tooltip'; import Swatch from './Swatch'; import './input.less'; @@ -46,9 +45,7 @@ export default function Input({ color, presetColors, presetColumns, onChange, on {map(presets, (group, index) => (
{map(group, ([title, value]) => ( - - validateColor(value, onChange)} /> - + validateColor(value, onChange)} /> ))}
))} diff --git a/client/app/components/ColorPicker/Swatch.jsx b/client/app/components/ColorPicker/Swatch.jsx index e23e5d8ead..f0b510b612 100644 --- a/client/app/components/ColorPicker/Swatch.jsx +++ b/client/app/components/ColorPicker/Swatch.jsx @@ -1,26 +1,37 @@ +import { isString } from 'lodash'; import React from 'react'; import PropTypes from 'prop-types'; +import Tooltip from 'antd/lib/tooltip'; import './swatch.less'; -export default function Swatch({ className, color, size, ...props }) { - return ( +export default function Swatch({ className, color, title, size, ...props }) { + const result = ( ); + + if (isString(title) && (title !== '')) { + return ( + {result} + ); + } + return result; } Swatch.propTypes = { className: PropTypes.string, + title: PropTypes.string, color: PropTypes.string, size: PropTypes.number, }; Swatch.defaultProps = { className: '', + title: null, color: 'transparent', size: 12, }; From 1799bfd98f8dd5ced168f72505495b065e89dd2f Mon Sep 17 00:00:00 2001 From: Levko Kravets Date: Fri, 13 Sep 2019 13:06:12 +0300 Subject: [PATCH 5/6] Use only object keys as tooltips; filter out invalid colors --- client/app/components/ColorPicker/Input.jsx | 20 +++++++++++++++----- client/app/components/ColorPicker/index.jsx | 4 ++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/client/app/components/ColorPicker/Input.jsx b/client/app/components/ColorPicker/Input.jsx index 5128cfa181..89af2ec5af 100644 --- a/client/app/components/ColorPicker/Input.jsx +++ b/client/app/components/ColorPicker/Input.jsx @@ -1,4 +1,4 @@ -import { isNil, isArray, chunk, map, toPairs, toString } from 'lodash'; +import { isNil, isArray, chunk, map, filter, toPairs } from 'lodash'; import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import tinycolor from 'tinycolor2'; @@ -9,8 +9,18 @@ import Swatch from './Swatch'; import './input.less'; function preparePresets(presetColors, presetColumns) { - presetColors = isArray(presetColors) ? map(presetColors, v => [toString(v), v]) : toPairs(presetColors); - return chunk(presetColors, presetColumns); + presetColors = isArray(presetColors) ? map(presetColors, v => [null, v]) : toPairs(presetColors); + presetColors = map(presetColors, ([title, value]) => { + if (isNil(value)) { + return [title, null]; + } + value = tinycolor(value); + if (value.isValid()) { + return [title, '#' + value.toHex().toUpperCase()]; + } + return null; + }); + return chunk(filter(presetColors), presetColumns); } function validateColor(value, callback, prefix = '#') { @@ -66,8 +76,8 @@ export default function Input({ color, presetColors, presetColumns, onChange, on Input.propTypes = { color: PropTypes.string, presetColors: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.string), - PropTypes.objectOf(PropTypes.string), + PropTypes.arrayOf(PropTypes.string), // array of colors (no tooltips) + PropTypes.objectOf(PropTypes.string), // color name => color value ]), presetColumns: PropTypes.number, onChange: PropTypes.func, diff --git a/client/app/components/ColorPicker/index.jsx b/client/app/components/ColorPicker/index.jsx index c64497bd33..946c3a5c33 100644 --- a/client/app/components/ColorPicker/index.jsx +++ b/client/app/components/ColorPicker/index.jsx @@ -103,8 +103,8 @@ ColorPicker.propTypes = { 'leftTop', 'leftBottom', 'rightTop', 'rightBottom', ]), presetColors: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.string), - PropTypes.objectOf(PropTypes.string), + PropTypes.arrayOf(PropTypes.string), // array of colors (no tooltips) + PropTypes.objectOf(PropTypes.string), // color name => color value ]), presetColumns: PropTypes.number, triggerSize: PropTypes.number, From 140a32e1c41f46ddfc88232076179c790fbe477b Mon Sep 17 00:00:00 2001 From: Levko Kravets Date: Mon, 16 Sep 2019 12:05:20 +0300 Subject: [PATCH 6/6] Fix alignment in dropdowns --- client/app/components/color-box.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/app/components/color-box.less b/client/app/components/color-box.less index 4684f0c193..d1bac3afbf 100644 --- a/client/app/components/color-box.less +++ b/client/app/components/color-box.less @@ -3,4 +3,8 @@ color-box { vertical-align: text-bottom; display: inline-block; margin-right: 5px; + + & ~ span { + vertical-align: bottom; + } }