import React from "react"; import { Popover } from "./Popover"; import "./ColorPicker.css"; import { KEYS } from "../keys"; import { t } from "../i18n"; // This is a narrow reimplementation of the awesome react-color Twitter component // https://github.com/casesandberg/react-color/blob/master/src/components/twitter/Twitter.js // Unfortunately, we can't detect keyboard layout in the browser. So this will // only work well for QWERTY but not AZERTY or others... const keyBindings = [ ["1", "2", "3", "4", "5"], ["q", "w", "e", "r", "t"], ["a", "s", "d", "f", "g"], ].flat(); const Picker = function({ colors, color, onChange, onClose, label, }: { colors: string[]; color: string | null; onChange: (color: string) => void; onClose: () => void; label: string; }) { const firstItem = React.useRef(); const activeItem = React.useRef(); const gallery = React.useRef(); const colorInput = React.useRef(); React.useEffect(() => { // After the component is first mounted // focus on first input if (activeItem.current) { activeItem.current.focus(); } else if (firstItem.current) { firstItem.current.focus(); } }, []); const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === KEYS.TAB) { const { activeElement } = document; if (e.shiftKey) { if (activeElement === firstItem.current) { colorInput.current?.focus(); e.preventDefault(); } } else { if (activeElement === colorInput.current) { firstItem.current?.focus(); e.preventDefault(); } } } else if ( e.key === KEYS.ARROW_RIGHT || e.key === KEYS.ARROW_LEFT || e.key === KEYS.ARROW_UP || e.key === KEYS.ARROW_DOWN ) { const { activeElement } = document; const index = Array.prototype.indexOf.call( gallery!.current!.children, activeElement, ); if (index !== -1) { const length = gallery!.current!.children.length; const nextIndex = e.key === KEYS.ARROW_RIGHT ? (index + 1) % length : e.key === KEYS.ARROW_LEFT ? (length + index - 1) % length : e.key === KEYS.ARROW_DOWN ? (index + 5) % length : e.key === KEYS.ARROW_UP ? (length + index - 5) % length : index; (gallery!.current!.children![nextIndex] as any).focus(); } e.preventDefault(); } else if (keyBindings.includes(e.key.toLowerCase())) { const index = keyBindings.indexOf(e.key.toLowerCase()); (gallery!.current!.children![index] as any).focus(); e.preventDefault(); } else if (e.key === KEYS.ESCAPE || e.key === KEYS.ENTER) { e.preventDefault(); onClose(); } e.nativeEvent.stopImmediatePropagation(); }; return (
{ if (el) { gallery.current = el; } }} > {colors.map((_color, i) => ( ))}
{ onChange(color); }} ref={colorInput} />
); }; const ColorInput = React.forwardRef( ( { color, onChange, label, }: { color: string | null; onChange: (color: string) => void; label: string; }, ref, ) => { const colorRegex = /^([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8}|transparent)$/; const [innerValue, setInnerValue] = React.useState(color); const inputRef = React.useRef(null); React.useEffect(() => { setInnerValue(color); }, [color]); React.useImperativeHandle(ref, () => inputRef.current); return (
#
{ const value = e.target.value.toLowerCase(); if (value.match(colorRegex)) { onChange(value === "transparent" ? "transparent" : `#${value}`); } setInnerValue(value); }} value={(innerValue || "").replace(/^#/, "")} onPaste={e => onChange(e.clipboardData.getData("text"))} onBlur={() => setInnerValue(color)} ref={inputRef} />
); }, ); export function ColorPicker({ type, color, onChange, label, }: { type: "canvasBackground" | "elementBackground" | "elementStroke"; color: string | null; onChange: (color: string) => void; label: string; }) { const [isActive, setActive] = React.useState(false); const pickerButton = React.useRef(null); return (
{isActive ? ( setActive(false)}> { onChange(changedColor); }} onClose={() => { setActive(false); pickerButton.current?.focus(); }} label={label} /> ) : null}
); } // https://yeun.github.io/open-color/ const colors = { // Shade 0 canvasBackground: [ "#ffffff", "#f8f9fa", "#f1f3f5", "#fff5f5", "#fff0f6", "#f8f0fc", "#f3f0ff", "#edf2ff", "#e7f5ff", "#e3fafc", "#e6fcf5", "#ebfbee", "#f4fce3", "#fff9db", "#fff4e6", ], // Shade 6 elementBackground: [ "transparent", "#ced4da", "#868e96", "#fa5252", "#e64980", "#be4bdb", "#7950f2", "#4c6ef5", "#228be6", "#15aabf", "#12b886", "#40c057", "#82c91e", "#fab005", "#fd7e14", ], // Shade 9 elementStroke: [ "#000000", "#343a40", "#495057", "#c92a2a", "#a61e4d", "#862e9c", "#5f3dc4", "#364fc7", "#1864ab", "#0b7285", "#087f5b", "#2b8a3e", "#5c940d", "#e67700", "#d9480f", ], };