@neo4j-ndl/react
Version:
React implementation of Neo4j Design System
223 lines • 11.7 kB
JavaScript
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