UNPKG

@neo4j-ndl/react

Version:

React implementation of Neo4j Design System

223 lines 11.7 kB
var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; /** * * Copyright (c) "Neo4j" * Neo4j Sweden AB [http://neo4j.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ import 'eyedropper-polyfill'; import { tokens } from '@neo4j-ndl/base'; import { hexToHsva, hsvaToHex, hsvaToRgba, Hue, rgbaToHex, rgbaToHsva, Saturation, validHex, } from '@uiw/react-color'; import classNames from 'classnames'; import { forwardRef, useEffect, useState } from 'react'; import { CleanIconButton } from '../clean-icon-button'; import { EyeDropperIconOutline } from '../icons'; import { Select } from '../select'; import { TextInput } from '../text-input'; export const ColorPicker = forwardRef(function ColorPicker({ color, onChange, swatches = Object.values(tokens.graph).filter(validHex), shouldShowEyeDropper = true, className, style, }, ref) { const [isEyeDropperActiveState, setIsEyeDropperActiveState] = useState(false); const [format, setFormat] = useState({ label: 'Hex', value: 'hex', }); const isEyeDropperActive = 'EyeDropper' in window && shouldShowEyeDropper; const hsva = typeof color === 'string' ? hexToHsva(color) : typeof color === 'object' && 'r' in color ? rgbaToHsva(color) : Object.assign(Object.assign({ h: 0, s: 0, v: 0 }, color), { a: 1 }); const handleChange = (hsva) => { const hex = hsvaToHex(hsva); const rgb = hsvaToRgba(hsva); onChange({ // used since typescript cannot infer the type of the color hex: `#${hex.split('#')[1]}`, hsva, rgb, }); }; return (_jsxs("div", { ref: ref, className: classNames('ndl-color-picker', className), style: style, children: [_jsx(Saturation, { hsva: hsva, className: "ndl-color-picker-saturation", onChange: (newColor) => { handleChange(Object.assign(Object.assign(Object.assign({}, hsva), newColor), { a: hsva.a })); }, pointer: (_a) => { var { left, top } = _a, props = __rest(_a, ["left", "top"]); return (_jsx(Pointer, Object.assign({ left: left === null || left === void 0 ? void 0 : left.toString(), top: top === null || top === void 0 ? void 0 : top.toString() }, props, { hsva: hsva }))); }, radius: tokens.borderRadius['lg'] }), _jsx(Swatch, { colors: swatches, hsva: hsva, onChange: (newColor) => { handleChange(newColor); } }), _jsxs("div", { className: "ndl-color-picker-hue-container", children: [isEyeDropperActive && (_jsx(CleanIconButton, { size: "small", isActive: isEyeDropperActiveState, onClick: () => { setIsEyeDropperActiveState(true); // TODO: Remove the any casting when polyfiller is not needed anymore. https://developer.mozilla.org/en-US/docs/Web/API/EyeDropper // Type assertion to handle the unknown type // eslint-disable-next-line @typescript-eslint/no-explicit-any const eyeDropper = new window.EyeDropper(); eyeDropper .open() .then((result) => { handleChange(hexToHsva(result.sRGBHex)); }) .catch((err) => { console.error(err); }) .finally(() => { setIsEyeDropperActiveState(false); }); }, description: "Pick color", children: _jsx(EyeDropperIconOutline, {}) })), _jsx(Hue, { className: "ndl-color-picker-hue", hue: hsva.h, onChange: (newHue) => { handleChange(Object.assign(Object.assign({}, hsva), { h: newHue.h })); }, radius: tokens.borderRadius['lg'], pointer: (props) => (_jsx(Pointer, Object.assign({}, props, { hsva: { a: 1, h: hsva.h, s: 100, v: 100, } }))) })] }), _jsxs("div", { className: "ndl-color-picker-inputs", children: [_jsx(Select, { size: "small", type: "select", ariaLabel: "Color format", style: { flexShrink: 0, }, selectProps: { isSearchable: false, onChange: (newFormat) => { if (!newFormat) { return; } setFormat({ label: newFormat.label, value: newFormat.value, }); }, options: [ { label: 'Hex', value: 'hex', }, { label: 'RGB', value: 'rgb', }, ], value: { label: format.label, value: format.value }, } }), format.value === 'hex' && (_jsx(HexInput, { hsva: hsva, onChange: (newColor) => { handleChange(hexToHsva(newColor)); } })), format.value === 'rgb' && (_jsx(RgbInput, { hsva: hsva, onChange: (newColor) => { handleChange(hexToHsva(newColor)); } }))] })] })); }); const removeHashPrefix = (hex) => hex.replace('#', ''); const HexInput = ({ hsva, onChange, }) => { const [inputValue, setInputValue] = useState(() => removeHashPrefix(hsvaToHex(hsva))); // Update input value when hsva changes from outside useEffect(() => { setInputValue(removeHashPrefix(hsvaToHex(hsva))); }, [hsva]); const handleChange = (e) => { const value = removeHashPrefix(e.target.value); // Always update the input value for immediate feedback setInputValue(value); // Check if the input is a valid hex color (only 6 characters) const isValidHex = /^[0-9A-Fa-f]{6}$/.test(value); if (isValidHex) { onChange(`#${value}`); } }; const hexValue = hsvaToHex(hsva); return (_jsx(TextInput, { size: "small", value: inputValue, leadingElement: _jsx("div", { className: "ndl-color-picker-hex-input-prefix", children: "#" }), onChange: handleChange, isFluid: true, htmlAttributes: { 'aria-label': 'Hex color code', maxLength: 6, onCopy: (e) => { e.preventDefault(); navigator.clipboard.writeText(hexValue); }, } })); }; const RgbInput = ({ hsva, onChange, }) => { const rgba = hsvaToRgba(hsva); const [rgbValues, setRgbValues] = useState({ b: rgba.b, g: rgba.g, r: rgba.r, }); // Update input values when hsva changes from outside useEffect(() => { const newRgba = hsvaToRgba(hsva); setRgbValues({ b: newRgba.b, g: newRgba.g, r: newRgba.r, }); }, [hsva]); const handleChange = (channel, e) => { const value = parseInt(e.target.value, 10); // Validate the input is a number between 0-255 const validValue = isNaN(value) ? 0 : Math.max(0, Math.min(255, value)); const newRgbValues = Object.assign(Object.assign({}, rgbValues), { [channel]: validValue }); setRgbValues(newRgbValues); // Convert RGB to hex and call onChange const hexColor = `#${newRgbValues.r.toString(16).padStart(2, '0')}${newRgbValues.g.toString(16).padStart(2, '0')}${newRgbValues.b.toString(16).padStart(2, '0')}`; onChange(hexColor); }; return (_jsxs("div", { className: "ndl-color-picker-rgb-inputs", children: [_jsx(TextInput, { size: "small", className: "ndl-color-picker-rgb-input", value: rgbValues.r.toString(), onChange: (e) => handleChange('r', e), htmlAttributes: { 'aria-label': 'Red', max: '255', min: '0', type: 'number', } }), _jsx(TextInput, { size: "small", className: "ndl-color-picker-rgb-input", value: rgbValues.g.toString(), onChange: (e) => handleChange('g', e), htmlAttributes: { 'aria-label': 'Green', max: '255', min: '0', type: 'number', } }), _jsx(TextInput, { size: "small", className: "ndl-color-picker-rgb-input", value: rgbValues.b.toString(), onChange: (e) => handleChange('b', e), htmlAttributes: { 'aria-label': 'Blue', max: '255', min: '0', type: 'number', } })] })); }; const Swatch = ({ colors, onChange, hsva, }) => { return (_jsx("div", { className: "ndl-color-picker-swatch", children: colors.map((color) => { const hexColor = typeof color === 'string' ? color : typeof color === 'object' && 'r' in color ? rgbaToHex(color) : hsvaToHex(color); const hsvaColor = typeof color === 'string' ? hexToHsva(color) : 'r' in color ? rgbaToHsva(color) : color; const isActive = hsva.h === hsvaColor.h && hsva.s === hsvaColor.s && hsva.v === hsvaColor.v; return (_jsx("button", { "aria-label": hexColor, className: classNames('ndl-color-picker-swatch-color', { 'ndl-color-picker-swatch-color-active': isActive, }), style: { backgroundColor: hsvaToHex(hsvaColor) }, onClick: () => onChange(hsvaColor) }, hexColor)); }) })); }; const Pointer = ({ prefixCls, left, top, hsva, }) => { const rgba = hsvaToRgba(hsva); return (_jsx("div", { style: { backgroundColor: `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`, left, top, }, className: classNames(prefixCls, 'ndl-color-picker-pointer') })); }; //# sourceMappingURL=ColorPicker.js.map