UNPKG

chayns-components

Version:

A set of beautiful React components for developing chayns® applications.

421 lines (416 loc) 13.5 kB
/** * @component */ import classNames from 'clsx'; import PropTypes from 'prop-types'; import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; import Bubble from '../../react-chayns-bubble/component/Bubble'; import { hexStringToHsv, hsvToHexString, hsvToRgb, rgbToHexString, rgbToHsv, rgbToRgbString } from '../../utils/color'; import { isString } from '../../utils/is'; import ColorArea from './colorArea/ColorArea'; import ColorInput from './colorInput/ColorInput'; import "./ColorPicker.css"; import HueSlider from './hueSlider/HueSlider'; import TransparencySlider from './transparencySlider/TransparencySlider'; import isDescendant from '../../utils/isDescendant'; import ColorSelection from './colorSelection/ColorSelection'; const getHsvColor = color => { if (isString(color)) { // HEX(A) return hexStringToHsv(color); } if (color.r !== undefined) { // RGB(A) (0-255) return rgbToHsv(color); } if (color.h !== undefined) { // HSV(A) return color; } return { h: 0, s: 0, v: 0, a: 1 }; }; /** * Lets a user choose a color for text, shapes, marking tools, and other * elements. */ const ColorPicker = /*#__PURE__*/forwardRef((_ref, reference) => { let { defaultColorModel, transparency = false, onChangeEnd, input = false, color, onBlur, inline = false, children, removeParentSpace = false, parent, onChange, style, className, bubblePosition = Bubble.position.BOTTOM_CENTER, bubbleClassName, bubbleStyle, showAllColorModels = false, customColorsArray, showCustomColors = false, showGlobalColors = false, onCreateCustomColor, onRemoveCustomColor } = _ref; // references const bubbleRef = useRef(null); const bubbleContentRef = useRef(null); const linkRef = useRef(null); const childrenRef = useRef(null); // state const [colorState, setColor] = useState(getHsvColor(color)); const [coordinates, setCoordinates] = useState({ x: 0, y: 0 }); const [colorModel, setColorModel] = useState(defaultColorModel !== null && defaultColorModel !== void 0 ? defaultColorModel : transparency ? ColorPicker.colorModels.RGB : ColorPicker.colorModels.HEX); const [customColorsState, setCustomColorsState] = useState(customColorsArray ? customColorsArray.map(c => getHsvColor(c)) : []); useEffect(() => { if (customColorsArray) { setCustomColorsState(customColorsArray.map(c => getHsvColor(c))); } else { setCustomColorsState([]); } }, [customColorsArray]); // effects (lifecycle methods) useEffect(() => { setColor(getHsvColor(color)); }, [color]); const closeBubble = useCallback(async event => { // Hide bubble and remove event listeners if click was outside of the bubble if (event.type === 'blur' || !(event.target === bubbleContentRef.current || isDescendant(bubbleContentRef.current, event.target))) { document.removeEventListener('click', closeBubble); window.removeEventListener('blur', closeBubble); if (bubbleRef.current) { bubbleRef.current.hide(); } if (onBlur) { onBlur(color); } if (chayns.env.isApp || chayns.env.isMyChaynsApp) { chayns.allowRefreshScroll(); } } }, [color, onBlur]); const openBubble = useCallback(async () => { if (inline) { return; } const ref = children ? childrenRef : linkRef; const rect = ref.current.getBoundingClientRect(); let newX = rect.left + rect.width / 2; let newY = rect.bottom + (chayns.env.isApp ? (await chayns.getWindowMetrics()).pageYOffset : 0); if (removeParentSpace) { const parentRect = (parent || document.getElementsByClassName('tapp')[0] || document.body).getBoundingClientRect(); newX -= parentRect.left; newY -= parentRect.top; } setCoordinates({ x: newX, y: newY }); bubbleRef.current.show(); // Add event listeners to hide the bubble document.addEventListener('click', closeBubble, { capture: true }); // window.addEventListener('blur', closeBubble); if (chayns.env.isApp || chayns.env.isMyChaynsApp) { chayns.disallowRefreshScroll(); } }, [inline, children, removeParentSpace, closeBubble, parent]); const onChangeCallback = useCallback(newColor => { setColor(newColor); if (onChange) { onChange(newColor); } }, [setColor, onChange]); const onCreateCustomColorCallback = useCallback(newColor => { if (onCreateCustomColor) { onCreateCustomColor(newColor); } }, [onCreateCustomColor]); const onRemoveCustomColorCallback = useCallback(newColor => { if (onRemoveCustomColor) { onRemoveCustomColor(newColor); } }, [onRemoveCustomColor]); const onColorModelToggle = useCallback(() => { setColorModel((colorModel + 1) % Object.keys(ColorPicker.colorModels).length); }, [setColorModel, colorModel]); const rgb255 = hsvToRgb(colorState); useImperativeHandle(reference, () => ({ show: openBubble })); if (inline) { return /*#__PURE__*/React.createElement("div", { className: classNames('cc__color-picker', className), style: { width: '322px', ...style }, onClick: openBubble, key: "div", ref: childrenRef }, /*#__PURE__*/React.createElement("div", { ref: bubbleContentRef, className: "cc__color-picker__bubble-content" }, /*#__PURE__*/React.createElement(ColorArea, { color: colorState, onChange: onChangeCallback, onChangeEnd: onChangeEnd }), /*#__PURE__*/React.createElement("div", { style: { padding: '0 11px' } }, /*#__PURE__*/React.createElement("div", { style: { display: 'flex', flexDirection: 'row' } }, /*#__PURE__*/React.createElement("div", { style: {}, className: transparency ? 'cc__color-picker__slider-container__with-transparency' : 'cc__color-picker__slider-container__without-transparency' }, /*#__PURE__*/React.createElement(HueSlider, { color: colorState, onChange: onChangeCallback, onChangeEnd: onChangeEnd, showTooltip: false }), transparency && /*#__PURE__*/React.createElement(TransparencySlider, { color: colorState, onChange: onChangeCallback, onChangeEnd: onChangeEnd })), transparency && /*#__PURE__*/React.createElement("div", { className: "cc__color-picker__color-square", style: { backgroundColor: hsvToHexString(colorState) } }))), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(ColorSelection, { customColorsArray: customColorsState, showCustomColors: showCustomColors, showGlobalColors: showGlobalColors, color: colorState, onChange: c => { onChangeCallback(c); onChangeEnd === null || onChangeEnd === void 0 ? void 0 : onChangeEnd(c); }, onCreateCustomColor: onCreateCustomColorCallback, onRemoveCustomColor: onRemoveCustomColorCallback }), input && /*#__PURE__*/React.createElement(ColorInput, { color: colorState, onChange: onChangeCallback, onChangeEnd: onChangeEnd, onModelToggle: onColorModelToggle, colorModel: colorModel, transparency: transparency, showAllColorModels: showAllColorModels })))); } return [/*#__PURE__*/React.createElement("div", { className: classNames('cc__color-picker', className), style: style, onClick: openBubble, key: "div", ref: childrenRef }, children || [/*#__PURE__*/React.createElement("div", { key: "circle", className: "cc__color-picker__color-circle", style: { backgroundColor: rgbToRgbString(rgb255, true) } }), /*#__PURE__*/React.createElement("div", { key: "link", className: "cc__color-picker__color-link chayns__color--headline chayns__border-color--headline", ref: linkRef }, colorModel === ColorPicker.colorModels.RGB ? rgbToRgbString(rgb255, transparency) : rgbToHexString(rgb255, transparency))]), /*#__PURE__*/React.createElement(Bubble, { ref: bubbleRef, coordinates: coordinates, position: bubblePosition, parent: parent, className: bubbleClassName, style: { width: '322px', ...bubbleStyle }, key: "bubble" }, /*#__PURE__*/React.createElement("div", { ref: bubbleContentRef, className: "cc__color-picker__bubble-content" }, /*#__PURE__*/React.createElement(ColorArea, { color: colorState, onChange: onChangeCallback, onChangeEnd: onChangeEnd }), /*#__PURE__*/React.createElement("div", { style: { padding: '0 11px' } }, /*#__PURE__*/React.createElement("div", { style: { display: 'flex', flexDirection: 'row' } }, /*#__PURE__*/React.createElement("div", { style: {}, className: transparency ? 'cc__color-picker__slider-container__with-transparency' : 'cc__color-picker__slider-container__without-transparency' }, /*#__PURE__*/React.createElement(HueSlider, { color: colorState, onChange: onChangeCallback, onChangeEnd: onChangeEnd, showTooltip: false }), transparency && /*#__PURE__*/React.createElement(TransparencySlider, { color: colorState, onChange: onChangeCallback, onChangeEnd: onChangeEnd })), transparency && /*#__PURE__*/React.createElement("div", { className: "cc__color-picker__color-square", style: { backgroundColor: hsvToHexString(colorState) } }))), /*#__PURE__*/React.createElement("div", { style: { marginBottom: '5px' } }, /*#__PURE__*/React.createElement(ColorSelection, { customColorsArray: customColorsState, showCustomColors: showCustomColors, showGlobalColors: showGlobalColors, color: colorState, onChange: c => { onChangeCallback(c); onChangeEnd === null || onChangeEnd === void 0 ? void 0 : onChangeEnd(c); }, onCreateCustomColor: onCreateCustomColorCallback, onRemoveCustomColor: onRemoveCustomColorCallback }), input && /*#__PURE__*/React.createElement(ColorInput, { color: colorState, onChange: onChangeCallback, onChangeEnd: onChangeEnd, onModelToggle: onColorModelToggle, colorModel: colorModel, transparency: transparency, showAllColorModels: showAllColorModels }))))]; }); const colorPropType = PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.shape({ r: PropTypes.number.isRequired, g: PropTypes.number.isRequired, b: PropTypes.number.isRequired, a: PropTypes.number }).isRequired, PropTypes.shape({ h: PropTypes.number.isRequired, s: PropTypes.number.isRequired, v: PropTypes.number.isRequired, a: PropTypes.number }).isRequired]); ColorPicker.propTypes = { /** * Display the color picker without a bubble. */ inline: PropTypes.bool, /** * The current color. Either a HEX-string, an HSV(A)- or RGB(A)-object. */ color: colorPropType.isRequired, /** * The bubble position. The possible values are listed under the * `Bubble`-component. */ bubblePosition: PropTypes.number, /** * Will be called when changing the color. */ onChange: PropTypes.func, /** * Will be called after the color was changed. */ onChangeEnd: PropTypes.func, /** * Will be called when the picker loses focus. */ onBlur: PropTypes.func, /** * Wether the picker should show a transparency slider. */ transparency: PropTypes.bool, /** * The parent node the bubble should be rendered into. */ parent: typeof Element !== 'undefined' ? PropTypes.instanceOf(Element) : () => {}, /** * The classname that will be set on the children wrapper. */ className: PropTypes.string, /** * A React style object that will be assigned to the children wrapper * element. */ style: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])), /** * A classname string that will be applied to the Bubble component. */ bubbleClassName: PropTypes.string, /** * A React style object that will be applied to the Bubble component. */ bubbleStyle: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])), /** * Displays an input to type in the color. */ input: PropTypes.bool, /** * The color model that is used by default. */ defaultColorModel: PropTypes.number, /** * Children // TODO */ children: PropTypes.node, /** * Removes space from the parent to the page borders from the tooltip * position. This is only needed if the parent is padded from the page and * has a relative positioning. */ removeParentSpace: PropTypes.bool, /** * Shows all color models */ showAllColorModels: PropTypes.bool, /** * An array of custom selectable colors */ customColorsArray: PropTypes.arrayOf(colorPropType), /** * Shows custom colors */ showCustomColors: PropTypes.bool, /** * Shows global colors */ showGlobalColors: PropTypes.bool, /** * Will be called when a custom color is added */ onCreateCustomColor: PropTypes.func, /** * Will be called when a custom color is removed */ onRemoveCustomColor: PropTypes.func }; ColorPicker.colorModels = { HEX: 0, RGB: 1 }; ColorPicker.displayName = 'ColorPicker'; export default ColorPicker; //# sourceMappingURL=ColorPicker.js.map