@neo4j-ndl/react
Version:
React implementation of Neo4j Design System
229 lines • 12.9 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;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ColorPicker = void 0;
const jsx_runtime_1 = require("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/>.
*/
require("eyedropper-polyfill");
const base_1 = require("@neo4j-ndl/base");
const react_color_1 = require("@uiw/react-color");
const classnames_1 = __importDefault(require("classnames"));
const react_1 = require("react");
const clean_icon_button_1 = require("../clean-icon-button");
const icons_1 = require("../icons");
const select_1 = require("../select");
const text_input_1 = require("../text-input");
exports.ColorPicker = (0, react_1.forwardRef)(function ColorPicker({ color, onChange, swatches = Object.values(base_1.tokens.graph).filter(react_color_1.validHex), shouldShowEyeDropper = true, className, style, }, ref) {
const [isEyeDropperActiveState, setIsEyeDropperActiveState] = (0, react_1.useState)(false);
const [format, setFormat] = (0, react_1.useState)({
label: 'Hex',
value: 'hex',
});
const isEyeDropperActive = 'EyeDropper' in window && shouldShowEyeDropper;
const hsva = typeof color === 'string'
? (0, react_color_1.hexToHsva)(color)
: typeof color === 'object' && 'r' in color
? (0, react_color_1.rgbaToHsva)(color)
: Object.assign(Object.assign({ h: 0, s: 0, v: 0 }, color), { a: 1 });
const handleChange = (hsva) => {
const hex = (0, react_color_1.hsvaToHex)(hsva);
const rgb = (0, react_color_1.hsvaToRgba)(hsva);
onChange({
// used since typescript cannot infer the type of the color
hex: `#${hex.split('#')[1]}`,
hsva,
rgb,
});
};
return ((0, jsx_runtime_1.jsxs)("div", { ref: ref, className: (0, classnames_1.default)('ndl-color-picker', className), style: style, children: [(0, jsx_runtime_1.jsx)(react_color_1.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 ((0, jsx_runtime_1.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: base_1.tokens.borderRadius['lg'] }), (0, jsx_runtime_1.jsx)(Swatch, { colors: swatches, hsva: hsva, onChange: (newColor) => {
handleChange(newColor);
} }), (0, jsx_runtime_1.jsxs)("div", { className: "ndl-color-picker-hue-container", children: [isEyeDropperActive && ((0, jsx_runtime_1.jsx)(clean_icon_button_1.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((0, react_color_1.hexToHsva)(result.sRGBHex));
})
.catch((err) => {
console.error(err);
})
.finally(() => {
setIsEyeDropperActiveState(false);
});
}, description: "Pick color", children: (0, jsx_runtime_1.jsx)(icons_1.EyeDropperIconOutline, {}) })), (0, jsx_runtime_1.jsx)(react_color_1.Hue, { className: "ndl-color-picker-hue", hue: hsva.h, onChange: (newHue) => {
handleChange(Object.assign(Object.assign({}, hsva), { h: newHue.h }));
}, radius: base_1.tokens.borderRadius['lg'], pointer: (props) => ((0, jsx_runtime_1.jsx)(Pointer, Object.assign({}, props, { hsva: {
a: 1,
h: hsva.h,
s: 100,
v: 100,
} }))) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "ndl-color-picker-inputs", children: [(0, jsx_runtime_1.jsx)(select_1.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' && ((0, jsx_runtime_1.jsx)(HexInput, { hsva: hsva, onChange: (newColor) => {
handleChange((0, react_color_1.hexToHsva)(newColor));
} })), format.value === 'rgb' && ((0, jsx_runtime_1.jsx)(RgbInput, { hsva: hsva, onChange: (newColor) => {
handleChange((0, react_color_1.hexToHsva)(newColor));
} }))] })] }));
});
const removeHashPrefix = (hex) => hex.replace('#', '');
const HexInput = ({ hsva, onChange, }) => {
const [inputValue, setInputValue] = (0, react_1.useState)(() => removeHashPrefix((0, react_color_1.hsvaToHex)(hsva)));
// Update input value when hsva changes from outside
(0, react_1.useEffect)(() => {
setInputValue(removeHashPrefix((0, react_color_1.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 = (0, react_color_1.hsvaToHex)(hsva);
return ((0, jsx_runtime_1.jsx)(text_input_1.TextInput, { size: "small", value: inputValue, leadingElement: (0, jsx_runtime_1.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 = (0, react_color_1.hsvaToRgba)(hsva);
const [rgbValues, setRgbValues] = (0, react_1.useState)({
b: rgba.b,
g: rgba.g,
r: rgba.r,
});
// Update input values when hsva changes from outside
(0, react_1.useEffect)(() => {
const newRgba = (0, react_color_1.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 ((0, jsx_runtime_1.jsxs)("div", { className: "ndl-color-picker-rgb-inputs", children: [(0, jsx_runtime_1.jsx)(text_input_1.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',
} }), (0, jsx_runtime_1.jsx)(text_input_1.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',
} }), (0, jsx_runtime_1.jsx)(text_input_1.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 ((0, jsx_runtime_1.jsx)("div", { className: "ndl-color-picker-swatch", children: colors.map((color) => {
const hexColor = typeof color === 'string'
? color
: typeof color === 'object' && 'r' in color
? (0, react_color_1.rgbaToHex)(color)
: (0, react_color_1.hsvaToHex)(color);
const hsvaColor = typeof color === 'string'
? (0, react_color_1.hexToHsva)(color)
: 'r' in color
? (0, react_color_1.rgbaToHsva)(color)
: color;
const isActive = hsva.h === hsvaColor.h &&
hsva.s === hsvaColor.s &&
hsva.v === hsvaColor.v;
return ((0, jsx_runtime_1.jsx)("button", { "aria-label": hexColor, className: (0, classnames_1.default)('ndl-color-picker-swatch-color', {
'ndl-color-picker-swatch-color-active': isActive,
}), style: { backgroundColor: (0, react_color_1.hsvaToHex)(hsvaColor) }, onClick: () => onChange(hsvaColor) }, hexColor));
}) }));
};
const Pointer = ({ prefixCls, left, top, hsva, }) => {
const rgba = (0, react_color_1.hsvaToRgba)(hsva);
return ((0, jsx_runtime_1.jsx)("div", { style: {
backgroundColor: `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`,
left,
top,
}, className: (0, classnames_1.default)(prefixCls, 'ndl-color-picker-pointer') }));
};
//# sourceMappingURL=ColorPicker.js.map